Recently I came up to something illogical, reading the latest ANSI C paper. It was talking about linkage but it never mentioned a way to declare internal identifiers inside block-scope (or at least in a useful way). Imagine something like this:

int main()

    //Here how to declare the identifier 'b'
    //with internal linkage   

    b = 2;

    printf("%d", b);

static int b;

If you try something like this:

    extern int b;

Then the this would be UB because the identifier b is declared both internal and external in the same TU (according to the current standard).

However reading the K&B white paper ("The C Programming Language Edition 1") I came up to this (at page 137):

Imagine a fragment of a compiler that manipulates a symbol table. Each identifier in a program has certain information associated with it, for example, whether or not it is a keyword, whether or not it is external and/or static, and so on. The most compact way to encode such information is a set of one-bit flags in a single char or int.

See the important part is that in this example (which shows how a compiler could be written) it is considered the possibility for an identifier to have both static and extern storage specifier (remember in the current ANSI C standard we can only have a single storage specifier). Which actually make a lot of sense. This way we could specify in a declaration whatever we are referring to the identifier with internal or external linkage. This would be useful in certain situations. Imagine we have internal and external variable with the same identifier. This way we could distinguish between the both:

int main()
    extern static int b; //here internal variable should be declared (!)

    b = 2; //here internal variable should be modified

        extern int b; //here external variable should be declared (!)

        b = 2; //here external variable should be modified

static int b; //internal variable named 'b' defined here

In the current ANSI C standard this is not supported and also there is some synthetic rule that if an identifier is declared with the extern specifier and there is already such identifier visible with some linkage — this linkage is inherit to the identifier currently declared.

This lead to code like this:

static int b;

int main()
    extern int b; //re-declaration of identifier 'b' with internal linkage

    b = 2;

Which is rather confusing at least. Writing such code you'll suppose that the identifier with external linkages is modified.

Note that just putting static in a block-scope declarations only alters the storage-type of the variable.

Here are the original ANSI C statements about this:


For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible,31) if the prior declaration specifies internal or external linkage, the linkage of the identifier at the later declaration is the same as the linkage specified at the prior declaration. If no prior declaration is visible, or if the prior declaration specifies no linkage, then the identifier has external linkage.

And at $

If, within a translation unit, the same identifier appears with both internal and external linkage, the behavior is undefined.

About storage-specifiers at $

At most, one storage-class specifier may be given in the declaration specifiers in a declaration, except that _Thread_local may appear with static or extern)

  • 4
    No, they did not make a mistake. It's best not to ask loaded questions. – David Hammen Jan 24 '16 at 19:47
  • 1
    It isn't loaded. I simply though, as I found the current C standard specification illogical, there could be some other explanation. Then I searched the presumably origin of it (the particular book stated above) and I found this text that imposes more reasonable approach to the linkage specifiers. Because of that I assumed there could be some mistake of making the standard as it is now. If there isn't such a mistake can somebody point me the reason that is it as it is now then? – FISOCPP Jan 24 '16 at 20:04
  • 2
    Asking Did ANSI C make a mistake ... is a loaded question. – David Hammen Jan 24 '16 at 20:15
  • I guess it depends on how are you looking at it. – FISOCPP Jan 24 '16 at 20:21

First things first: The original K&R was written in 1978. Expecting that a book written in 1978 is authoritative now is a bad expectation.

The C language was an offshoot of B, which in turn was an offshoot of BCPL. In B, the auto keyword was essentially the opposite of the extrn keyword. That is perhaps the best way to think of the modern C extern keyword when used in block scope.

Per this most excellent answer at stackoverflow.com, the primary purpose of the auto keyword nowadays is to declare a vehicle:

auto vehicle_type my_car;

A car that you just bought and that you need to register with the department of motor vehicles:

register vehicle_type my_brand_new_car;

Your 1978 jalopy:

static vehicle_type my_very_old_car;

A car that doesn't belong to you, that has been registered with the DMV, and that is somewhat modern:

extern vehicle_type taxicab;

Finally, there are illegal declarations against which the compiler must issue a complaint:

extern static register auto vehicle_type car_flooded_by_a_hurricane;

Tongue out of cheek, the extern keyword has different meanings in block scope versus file scope. That shouldn't be surprising; the static keyword also has different meanings in block scope versus file scope. In both cases, the context (block scope versus file scope) is clear, so the compiler knows how to interpret those keywords. (And presumably, so does the human author / reader of the code in question.)

At file scope, qualifying some declarations with the extern keyword means that the thing being declared is defined in some other compilation unit. Resolving that is a problem for the linker to solve, as opposed to the compiler. At block scope, the extern keyword instead refers to something declared at a higher level, or at program level if there is no higher level declaration. If the compiler can resolve the reference, it will. If it can't, it will defer the issue of resolving the reference to the linker.

Update: Language lawyer details

The keywords auto, extern, register, static, and typedef are storage-class-specifiers. A symbol can be qualified by at most one storage-class-specifier. The declaration extern static int foo makes no sense (let alone extern static register auto int foo), and that is not what K&R meant by an externally static variable.

K&R (Kernighan and Ritchie) is aimed at normal human readers. When they wrote about external static variable (non-code font), they meant a variable that is not automatic, not a register, and not visible to the linker. They did not mean extern static (code font), let alone my extern static register auto vehicle_type car_flooded_by_a_hurricane. That did not make sense even in 1978, let alone 2016.

What K&R meant by an "externally static variable" is a variable declared at block scope qualified with the extern keyword that was previous declared at file scope as static. There was no mistake in 1978, and there is no mistake almost 40 years later.

  • Yeah but the identifier can be both defined in another TU as external and in the current one as internal. How can you differentiate between the two in the way you have described? – FISOCPP Jan 24 '16 at 18:54
  • @FISOCPP - extern at block scope does not necessarily mean something with external linkage. At block scope, extern refers to something declared at a higher scope. Qualifying something at extern at block scope only refers to something with external linkage in the special case that there is no visible declaration at a higher scope. – David Hammen Jan 24 '16 at 19:46
  • Who's saying? Maybe you could refer to your sources. As you see I haven't come up with this by myself. – FISOCPP Jan 24 '16 at 20:25
  • @FISOCPP - You are apparently interpreting K&R's "externally static" to mean extern static. That is incorrect. extern static is illegal (also, extern auto, static auto, etc.) At most one of the storage class specifiers (auto, extern, register, static, and typedef) can be used to qualify any one variable. – David Hammen Jan 24 '16 at 20:36
  • 2
    K&R was written for humans. The standard is written primarily for language lawyers and compiler writers. I'm not sure if they qualify as "human". – David Hammen Jan 24 '16 at 20:36

Not the answer you're looking for? Browse other questions tagged or ask your own question.