Previous Table of Contents Next


Feeling a Little C-sick?

C syntax flies in the face of the "say what you mean, mean what you say" principle of programming. The keyword extern is simply an abbreviation for "external" — a word you'd expect to be related to visibility or scope and to mean something like "outside the current context." And you'd expect the other relevant keyword, static, to relate to how storage is managed. Thus, you might read the declaration

extern int x;

to mean the variable x is visible outside the block or file in which it's declared. Likewise, for a declaration without extern (C has no intern keyword), such as

int x;

you'd expect the variable x not to be visible outside the block or file in which it's declared. For variables declared within a function or nested block, this sensible interpretation holds. The problem arises for variables declared outside any function. (In C, such declarations are called external declarations.) In most cases, when the two previous examples appear as external declarations, both the extern and non-extern declarations mean the variable is visible outside the file in which it's declared. (If the extern declaration isn't the first declaration of x, however, and the first declaration of x isn't visible outside the file, the extern declaration also specifies that x isn't visible outside the file — a further complexity in C's approach to visibility.) Some programmers wise in the ways of C might argue, "But both declarations are external declarations, so it is consistent that they both are visible `externally' to the file." Nice try. However, when the declaration

static int x;

is outside any function, it also is an external declaration, yet it defines a variable that is not visible outside the file.

Not only does C's syntax lack consistency, but it also confuses things by using the static storage class keyword to specify visibility. My theory is that Humpty Dumpty was on the original C design team. As he told Alice, "When I use a word, it means just what I choose it to mean — neither more nor less."

There are other confusing cases, as in the second declaration of x below,

static int x;
main( void ) {
extern int x;
...
}

where the use of extern results in internal linkage. I'll spare you a diversion down the cul-de-sac you enter when you have more than one external declaration for the same variable (which C allows). As we pass through the maze, however, notice that it matters whether you initialize a variable in an external declaration when you specify extern. (With an initializer, the declaration allocates storage; without one, it doesn't.) Yet when you specify static or no storage class, initialization doesn't effect storage allocation.

Although I've wandered repeatedly around the C maze using guides such as the SAA C reference manual and Kernighan and Ritchie's description of C (see the Bibliography), I still can't guarantee that Figure 5.1 is a perfect map of every C visibility rule. But there are better ways to simplify C variable declarations and make your programs more readable than building a mental map of C's visibility labyrinth.

UnC-eemly Solutions

The fastest way out of C's maze is to decide where you want to go — that is, what kinds of visibility you want to use. I've found three "classes" of visibility adequate to define most variables and functions:

*  Local: visible only within a single function or nested block.
*  Share: visible in all functions in a file, but not outside the file.
*  Export: visible in all functions in a program (all files). Export visibility implies share visibility. The file that "exports" an object allocates its storage.

You pick an appropriate one of these visibility classes when you define a variable. If a variable is local, you generally are done with your declarations for that object. In C, you define most local variables at the beginning of a function, and they are visible throughout that function (but not outside it).

If a variable has share or export visibility, however, you need one more concept — import declarations — to specify you intend to reference the variable in a specific function. One use of the "import" concept is to specify you want to use an export variable in a file other than the one containing its definition. Naturally, a file can't import a variable that isn't exported by some other file, and a file doesn't need to import variables that it exports — such variables already are available for shared use within the file. Note that in C programs, only one file can export a particular variable (this isn't true for all languages). Another use of the import concept is to specify you want to use a nonlocal variable in a function or block. After covering a couple of general rules relating to visibility, I'll explain a simple way to implement local, share, and export visibility, as well as import declarations.

The most important rule for declarations in C, or any language, is: Use the most restricted visibility possible for variables; avoid shared variables. In C, this most often means defining variables used in a function as local to that function by putting their declarations at the beginning of the function and using auto or no storage class specifier. The same rule and technique apply to nested blocks within a function.

In the special case of references to variables within a nested block that aren't local to the nested block, I usually do not declare the variables in the nested block. This results in implicit extern declarations for any variable you reference but don't declare in the nested block. When you follow this and other guidelines I present below, all references to variables not declared in a nested block resolve to a local or import declaration in an outer block. I rarely use nested blocks, except with "loop" macros, such as those I presented in Chapter 3. And in those cases, import declarations don't add any real protection or improve clarity. However, if you prefer to explicitly declare every variable in a nested block, you can either use the IMPORT macro (which I'll introduce in a moment) or define a similar macro to more specifically express your intent.

You should define share and export variables before the first function definition in a file, although C permits the confusing and dangerous practice of placing external declarations between functions, as in the following example:

void func1( void ) {
...
}
int x;
void func2( void ) {
...
}
void func3( void ) {
...
}

This ordering makes x implicitly visible in func2 and func3, but not visible in func1, unless x is explicitly declared extern within func1. The intent of such coding usually is to declare x as a variable shared only by some — not all — of the functions in a file. If several functions are coupled by sharing data you don't want other functions to access, put the coupled functions in a file by themselves, along with the external declarations for their shared variables.


Previous Table of Contents Next