Objects

Object declarations are much more complex than functions, because they not only determine linkage, but also may provide an object definition, a tentative definition, an initial value, and storage duration.

File-Scope Declarations

Objects declared at file-scope always have static storage duration, and, like functions, external linkage unless declared with the static storage class specifier. There are three possible scenarios to consider:

  1. The object has internal linkage and is defined in the current translation unit.

  2. The object has external linkage and is defined in the current translation unit.

  3. The object has external linkage and is defined in another translation unit.

each of which is denoted by a particular syntax,

  1. The static storage class specifier, with or without an initializer

  2. No storage class specifier, with or without an initializer.

  3. The extern storage class specifier, without an initializer.

In any case, if an initializer is provided, it constitutes a definition, which is why case 3 requires the absence of an initializer–otherwise it is equivalent to case 2. If an initializer is not provided, the first two cases (static or no storage class) constitute tentative definitions. If an object with a tentative definition is not explicitly defined by the end of a translation unit then it is implicitly defined as if it were initialized with a value of zero.

As an important edge-case, if an identifier is first declared static, a subsequent extern declaration of the same identifier does not change its linkage, and instead refers to the internally linked object from the earlier static declaration.

int x;        /* External, tentatively defined with an initial value of 0. */
static int y; /* Internal, tentatively defined with an initial value of 0. */
extern int z; /* External, not defined--must be defined in another translation unit */

extern int y; /* Internal, redeclaration retains linkage of earlier declaration. */

Students have often been admonished not to use global variables; to be clear, it is only providing an externally linked definition that is dangerous. There must be exactly one definition for every externally linked identifier that is ever accessed in an entire program; this is called the one definition rule (ODR). If two translation units (source files) in a program define an externally linked object with the same name, the program will not compile. This is known colloquially as “linker hell”.

Function Parameter Lists

Objects may also be declared within function parameter lists. These objects always have automatic storage duration and no linkage,

int sum(int a, int b) /* a and b are automatic variables */
{
   return a + b;
}

The only storage class specifier that may be applied is register and denotes that access to those objects should be as fast as possible. The register storage class specifier prohibits taking the address of an object, which ensures that the compiler is able to place it in a register without ever storing it to memory, if it is able to so. For example, the following function might be faster because it could result in arguments passed in registers rather than on the stack: int sum(register int a, register int b) { return a + b; }. Declaring an object as register does not affect its linkage or storage duration.

Block-Scope Declarations

All other object declarations appear within the body of a function; for example,

void swap(int a, int b)
{
   int tmp = a;
   a = b;
   b = tmp;
}

Automatic local variables

When no storage class specifier is provided, the object has automatic storage duration and no linkage; these are also called local or automatic variables. If no initializer is provided, the default value of an automatic variable is indeterminate.

Stateful functions

If the storage class specifier is static, the object still won’t have linkage, but will have static storage duration and will be zero-initialized if not provided an initial value. Static local variables preserve state between function calls,

int count()
{
   static int i;
   return i++;
}

int main()
{
   printf("%d\n", count()); /* "0" */
   printf("%d\n", count()); /* "1" */
   printf("%d\n", count()); /* "2" */
}

External variables

The storage class extern behaves the same as with file-scope declarations. This can be used to declare externally linked objects for use in a function without polluting the namespace at file-scope,

int accumulate(int x)
{
   extern int sum;
   sum += x;
   return sum;
}

Note, unlike with file-scope declarations, it is illegal to initialize (define) in an extern declaration inside a function; e.g.

void f() {
   extern int y = 0; /* ERROR */
}