So, in the end, the word "linkage" is used to govern the relationship
between translation units and identifiers ...
Right: linkage tells you where, within and between separate
translation units, the identifier will be "linked" to another
identifier that is spelled sufficiently similarly.
... and the word "scope" refers to a single translation unit
Yes...
and governs how identifiers are limited in their namespace?
I think it is closer to say that scope tells you where the name
can be "seen". There are only two "important" scopes: block and
file. (There are two more, "function scope" and "function prototype
scope". The former is strictly for goto-labels, and the latter is
a bit of a hack, being a substitute for block scope where there is
no brace-pair {...} to mark a block, but instead are just the
parentheses (...) that mark the function prototype. Function
prototype scope is thus really just "block scope in drag". Meanwhile,
"function scope" is really just "the outermost block of the containing
function" scope, and hence again is actually just a form of block
scope.)
An identifier that has file scope can be seen from whatever point
it is declared up to the end of the translation unit. An identifier
with block scope can be seen up until the block is closed. (As
noted above, a "function prototype" is closed by a closing parenthesis,
and the "function scope" for a goto label is closed by the closing
brace that ends the function body. So these identifiers' scopes
end in exactly the same way as block-scope identifiers.)
There is a connection -- exactly how deep or strong can be debated
-- between the concepts, as an identifier with block scope (or the
pseudo-block-scope that you get with function and function-prototype
scopes) normally has "no linkage". In order to obtain linkage, you
usually have to use file scope. (But see below.)
The main glitch in all this is that any identifier can be obscured by
another identically-spelled identifier that is declared in an
inner scope. For instance:
int file_scope_external_linkage;
static double file_scope_internal_linkage;
/* note: function identifiers always have file scope */
void func_external_linkage(void) {
char block_scope; /* no linkage */
block_scope = 'a';
{
float block_scope; /* also no linkage */
block_scope = 3.14159265;
}
}
Here there are two separate things, both named "block_scope", that
are still separate things. They both nave no linkage, and the
"outer" identifier named "block_scope" *would* be useable inside
the inner braces, except that it is "shadowed" by the inner name.
The char variable would be "visible" in the inner block; you just
cannot refer to it by name because the name is "captured" by an
"even-more-in-scope / more-visible" float variable.
The second glitch in this is the "extern" keyword. The meaning
of the "extern" keyword is, in a word, convoluted. In *most* cases
it means the obvious thing -- "external", as in "external linkage".
This gives you the ability to declare an identifier with external
linkage, yet block scope:
void somefunc(void) {
extern int somevar; /* block scope but external linkage */
...
}
In one case, though, "extern" actually means the same thing as the
"static" keyword. If (and only if) you use extern on a file scope
identifier that has already been declared as having internal linkage
(i.e., where the internal linkage is in scope), the "extern" keyword
gives that identifier internal linkage again (redundantly):
static int useless_var; /* file scope, internal linkage */
extern int useless_var; /* file scope, *internal* linkage (!) */
If you reverse the order, "bad things" happen:
extern int shared_var; /* file scope, external linkage */
static int shared_var; /* ERROR */
This attempts to give "shared_var" both internal and external
linkage. No diagnostic is required, and the behavior is undefined.
(The simplest rule here is "don't use both extern and static on
the same name". You only need to look up the exact rules about
how this all works when you are dealing with "bad" code. Even
then, the undefined aspect means that you may have to find out
what your particular implementation did, in order to figure out
what is going on with a given program.)
(I would argue that "avoid using the extern keyword inside block
scope" is also a good rule, although for temporary hacks, it can
be useful. The mixing of extern and static is *not* useful: the
well-defined version can be rewritten to just use the "static"
keyword each time, resulting in more-understandable code, at no
cost in code size or complexity.)