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
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.
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.
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 … }
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;
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.


No comments
Flux de commentaires pour cet article