Scope determines what you can see within a translation unit. Linkage determines what you can see across translation units.
A name declared in C or C++ may have attributes such as type, scope, storage duration, and linkage. Not every name has all of these attributes. For example, a function name has a type, a scope, and a linkage, but no storage duration. A statement label name has only a scope.An object’s type determines the object’s size and memory address alignment, the values the object can have, and the operations that can be performed on that object. A function’s type specifies the function’s parameter list and return type.A name’s scope is that portion of a translation unit in which the name is visible.1 (A translation unit is the source code produced by the preprocessor from a source file and all its included headers.) C supports four different kinds of scope: file scope, block scope, function prototype scope, and function scope. C++ generalizes file scope into namespace scope, and adds class scope.An object’s storage duration determines the lifetime of the storage for that object.2 That is, it determines how and when during program execution the storage for that object comes and goes. Each object in C and C++ has one of the following three storage durations: static, automatic, and dynamic. Only objects have storage duration. Enumeration constants, functions, labels, and types don’t.Which brings us to linkage. A name’s linkage affects whether two or more declarations for that name are valid, and if so, whether they refer to the same entity (such as a function or an object). Understanding linkage clears up a number of misconceptions about the behavior of C and C++ programs.

In my earlier article on the scope rules,1 I explained the distinction between declarations and definitions, but omitted some details. Those details are pertinent to the concept of linkage, so let’s take a look at them first.

DECLARATIONS AND DEFINITIONS
A declaration associates attributes with names. A declaration either introduces a name into the current translation unit or redeclares a name introduced by a declaration that appeared earlier in the same translation unit. A declaration for a name might also be a definition. Informally, a definition is a declaration that not only says « here’s a name, » but also « here’s all the information the compiler needs to create the code for that name ».

C++ lets you declare all sorts of things that aren’t valid in C, such as classes, namespaces, and linkage specifications. Not surprisingly, these features make the distinction between definitions and other declarations, as well as the linkage rules, more complicated in C++ than in C. However, we don’t really need to consider all those features to explain linkage–using just functions and objects should provide adequate illustration.

For function declarations that are valid in both C and C++, the rule for deciding when a function declaration is also a definition is the same for both languages, namely, that a function declaration is also a function definition if it includes a brace-enclosed body, as in:

int abs(int v)     {     return v < 0 ? -v : v;     };

A function heading without a body, as in:

int abs(int v);

is just a declaration.

For object declarations that are valid in both C and C++, the rule for deciding when an object declaration is also a definition is–surprisingly–simpler in C++ than in C. In C++, an object declaration is also a definition unless it contains an extern specifier and no initializer.

For example, all of the following object declarations are also definitions in C++:

 int i;                 // definition static int j;          // definition extern int k = 0;      // definition

They are definitions whether they appear at file scope, namespace scope, or block scope.

The following object declaration is not a definition:

 extern int m;          // non-defining declaration  
Again, the rule I just cited is how C++ distinguishes object definitions from other object declarations. The corresponding rule in C is complicated by the added presence of these things called tentative definitions. A tentative definition is an object declaration that might also be a definition, or it might not, depending on the presence of other definitions. In truth, the previous declarations for i and j are tentative definitions in C. C++ doesn’t recognize tentative definitions, so some combinations of object declarations that are valid in C aren’t valid in C++.The C++ rule for distinguishing object definitions from other object declarations works equally well in C as in C++. If you follow that rule in C, you won’t go wrong.THE CONCEPT OF LINKAGE
In some cases, C and C++ let you declare the same entity more than once, in different translation units, different scopes of the same translation unit, or even in the same scope. For example, the common C technique for packaging a library function is to declare the function in a header:

// foo.h  int foo(int n);

and define the function in a corresponding source file. That source file should include the header, as in:

// foo.c #include "foo.h"  int foo(int n)     {     ...     }

to ensure that the function defined in the source is working with the same declaration(s) as in the header. After preprocessing, the translation unit contains:

int foo(int n);    // from the header  int foo(int n)     {     ...     }

which declares the function twice. The second declaration is also the definition. From the compiler’s viewpoint, these are two declarations for the same function in the same scope. They refer to the same function because they have linkage.

C and C++ provide for three levels of linkage:

• A name with no linkage denotes an entity that can’t be referenced via names from anywhere else.

• A name with internal linkage denotes an entity that can be referenced via names declared in the same scope or in other scopes of the same translation unit.

• A name with external linkage denotes an entity that can be referenced via names declared in the same scope or in other scopes of the same translation unit (just as with internal linkage), or additionally in other translation units.

Both function and object names can have either internal or external linkage. Object names can also have no linkage. Beyond that, all other names in C have no linkage. In contrast, other names in C++ can have external linkage, including names for classes, enumeration types and constants, namespaces, references, and templates. References and function templates can also have internal linkage.

LINKAGE FOR FUNCTIONS
A function declared without a storage class specifier normally has external linkage by default. Placing the keyword extern in a function declaration such as:

extern int foo(int n);

has no effect. With or without the extern specifier, you can place this function declaration in a header, include that header in several source files, compile each file, and link the compiled modules together. Subsequently, all calls to that foo from any of those source files will call the same function.

A function declared at file or namespace scope with the keyword static has internal linkage. That is, when you declare:

static int foo(int n);

all calls to foo in that translation unit will be to a function foo defined in that same translation unit. If the translation unit contains calls to foo, yet function foo isn’t defined in that translation unit, the program won’t compile or link, even if there’s a definition for function foo in another translation unit.

View the full-size image

Table 1 illustrates how C and C++ determine the linkage for a function from the storage class specifier and the scope of the function’s declaration. For example, the row labeled extern shows that a function declared extern doesn’t always have external linkage. The row labeled none shows that a function declared without a storage class specifier at file, namespace, or block scope behaves as if it were declared with the keyword extern.

For example, a single translation unit can contain the following declarations:

static int foo(int n);    // internal linkage ...  extern int foo(int n);    // still internal

The extern declaration could be in an inner scope, as in:

static int foo(int n);    // internal linkage ...  int bar(int m)     {     extern int foo(int n); // still internal     ...     }
Written either way, these declarations are valid in both C and C++, and foo has internal linkage. However, just because you can do this doesn’t mean you should. Writing function declarations that differ only in their storage class specifiers is rarely, if ever, necessary, and can be confusing.If a single identifier has both internal and external linkage in the same translation unit, the resulting program has undefined behavior. That is, it’s an error, but you might not get an error message from the compiler or linker. For example, reversing the order of the extern and static declarations from the previous example, as in:

extern int foo(int n);   // external linkage ...  static int foo(int n);   // undefined behavior

yields undefined behavior. All the more reason not to write things like this.

LINKAGE FOR OBJECTS
Table 2 illustrates how C and C++ determine linkage and storage duration for an object from the storage class specifier and the scope of the object’s declaration. It shows, for example, that a non-const object declared at file or namespace scope without a storage class specifier, such as:

int n;

has external linkage and static storage duration. This declaration is also a definition in C++ but only a tentative definition in C.

View the full-size imageWith many C compilers, you can place this declaration in a header, include that header in several source files, compile each source file, and link them together. The linker will treat one of those tentative definitions as a definition and all the other tentative definitions as declarations referring to that definition. This behavior is a common extension to the C standard, but may not be portable to all C environments, and doesn’t work in C++. Using C++, you’ll get a link error complaining about a multiply-defined symbol.

If you want the code to compile and link in C++ as well as C, you must declare n with the extern specifier, as in:

extern int n;
As before, n has external linkage, but now the declaration is not a definition–not even a tentative one in C. You can place this declaration in a header, include that header in several source files, compile each source file, and link them together. But you’ll still get a link error, unless there’s exactly one definition for all those declarations to reference. Just one of the source files must provide a definition for n. Any one of the following definitions will do:

int n; int n = 0; extern int n = 0;

An object declared at block scope without a storage class specifier has automatic storage duration and no linkage. For example, in:

static int n;      // internal linkage  int foo(int v)     {     int n;         // no linkage     ...     }

both parameter v and local object n have no linkage. You can’t write a declaration for an integer v elsewhere and get it to link to foo‘s parameter v. Likewise for n. Thus, the two objects named n in this example are distinct objects.

If you add the static storage class specifier to the declaration of n at block scope, you change its storage duration but not its linkage. For example:

static int n;      // still internal linkage  int foo(int v)     {     static int n;  // still no linkage     ...     }

defines two distinct objects named n, both with static storage duration. The n at block scope still has no linkage.

There’s more to say about linkage, but this is enough for now.

Endnotes:

1. Saks, Dan, « Scope regions in C and C++, » Embedded Systems Design, November, 2007, p. 15. Available online at www.embedded.com/columns/programmingpointers/202600398.

2. Saks, Dan, « Storage class specifiers and storage duration, » Embedded Systems Design, January, 2008, p. 9. Available online at www.embedded.com/columns/programmingpointers/205203843.

Acknowledgments:

My thanks to Steve Adamczyk of the Edison Design Group (www.edg.com), Tom Plum of Plum Hall (www.plumhall.com), and Joel Saks (www.joelsaks.com) for their valuable assistance with this article.