7

This code will compile and is well defined under current C standards:

static int foo(int);
extern int foo(int);

The standard specifies that in this situation (C11: 6.2.2 Linkages of identifiers (p4)):

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. [...]

... which means that the int foo(int) function is declared static int foo(int).

Swapping these declarations around like this:

extern int foo(int);
static int foo(int);

...gives me a compiler error using GNU GCC:

static declaration of 'foo' follows non-static declaration

My question is: What is the design rationale behind the second case being an error and not handled in a similar way as the first case? I suspect it has something to do with the fact that separate translation units are easier to manage and #include? I feel as though without understanding this, I can open myself up to some mistakes in future C projects.

Dean P
  • 1,841
  • 23
  • 23
  • Your 'which means that the `int foo(int)` function is declared `extern int foo(int)`' comment is wrong. The prior `static` declaration means that the function is `static`. If you only declare externally visible functions in a header (where the declarations belong) and only declare static functions inside a source file, you won't ever run into problems. Headers don't declare static functions (unless the header defines `static inline` functions). You can only run into problems if you are sloppy in the way you write the code. The C compiler allows you to be sloppy, but you shouldn't be. – Jonathan Leffler Apr 25 '17 at 18:59
  • Similar comments apply to variables. Don't write `extern` declarations of variables in source files; they belong only in header files. – Jonathan Leffler Apr 25 '17 at 19:02
  • @JonathanLeffler "The prior static declaration means that the function is static". That's correct, it was a typo. Edited. – Dean P Apr 25 '17 at 19:10
  • @JonathanLeffler I am tempted to strongly disagree with it being bad for headers to define plainly static functions. For the most part, `static inline` is just a more verbose and less portable way of saying `static`, because the *semantics* of `inline` are *approximately nothing*, while `static` has very significant semantics in the language, and therefore actually communicates very useful information to an optimizing compiler. Relevantly, with optimizations turned up high enough in compilers like `gcc` and `clang`, `static` does approximately infinitely more to inline functions than `inline`. – mtraceur May 26 '22 at 10:45
  • @JonathanLeffler To be clear, I recognize that `inline` has *some* effects and semantics, but as far as I know those only change stuff for `extern` functions, and if the definition of the function occurs multiple times in the same translation unit - but that's what include guards in headers are for. Anyway, personally, I've yet to run into issues with headers which have include guards and define `static` helper functions without `inline`, but maybe I'm missing some edge case? – mtraceur May 26 '22 at 11:00

2 Answers2

4

I think the idea of this confusing specification is that an extern declaration can be used inside a function to refer to a global function or object, e.g to disambiguate it from another identifier with the same name

static double a; // a declaration and definition

void func(void) {
  unsigned a;
  .....
  if (something) {
     extern double a; // refers to the file scope object

  }
}

Whereas if you use static you declare something new:

extern double a;  // just a declaration, not a definition
                  // may reside elsewhere
void func(void) {
  unsigned a;
  .....
  if (something) {
     static double a; // declares and defines a new object

  }
}
Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • Makes sense. Want to see what the others say first but I think so far this is most plausible. – Dean P Apr 25 '17 at 19:11
  • While all of this is technically correct it seems fairly far-fetched: I'm not sure I have ever locally declared an external identifier -- if so, it was probably a function when I didn't want to include the header for some reason, and there certainly was no prior static declaration of it. Both cases look equally like an inadvertent name collision to me. – Peter - Reinstate Monica Jun 30 '20 at 11:34
  • @RobertSsupportsMonicaCellio When I said "collision" I didn't necessarily mean "illegal". Both examples are unusual and to be avoided, and as such provide a poor rationale for a language rule. – Peter - Reinstate Monica Jun 30 '20 at 13:20
3

I can imagine that the scenario leading to this asymmetry is that the default linkage of identifiers at global scope is extern.1 Failing to mark the definition of a previously static-declared function static as well would otherwise be an error because it is by default also an extern declaration.

Here is an illustration. In modern C, functions must be declared before use, but sometimes the implementation is at the end because it is secondary to the main purpose of the code in the file:

static void helper_func();       // typically not in a header

// code using helper_func()

// And eventually its definition, which by default 
// declares an **external** function. Adding 
// an explicit `extern` would not change a thing; it's redundant.
void helper_func() { /* ... */ }

This looks innocent enough, and the intent is clear. When C was standardized there was probably code around looking like this. That's why it is allowed.

Now consider the opposite:

extern void func(); // this could be in a header

// ... intervening code, perhaps a different file ...

static void func() { /* ... */ }

// code which uses func()

It's pretty clear that this should not be allowed. Defining a function static is a clear, explicit statement. A prior, contradicting extern declaration does not make sense.2 There is a good chance that this is an inadvertent name collision, for example with a function declared in a header. Probably not much code out there was looking like this at the time of formalization. That's why it is forbidden.

1 From the C17 draft, 6.2.2/5: "If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the storage-class specifier extern."

2 One could argue that an explicit extern declaration using the keyword, followed by a static definition, should be forbidden while an implicit one, without the keyword, could still be allowed (and that, correspondingly, a later explicitly extern function definition after a static declaration should be forbidden, while implicit ones are still allowed). But there is a limit to the hair-splitting a standard should do (and it's gone pretty far already).

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • I'm personally not arguing about that an explicit `extern` declaration using the keyword should be forbidden. Not at all, I like clarification whenever possible and this is a good feature to do so. What I complain with is that a `static` declared function should not be able to be declared explicitly with `extern` at its definition, since this adds confusion and is redundant. There is a difference. --- I personally also disagree about the last sentence. Many things I encounter could have been more elaborate. But apart from that, this is an excellent answer, I wish were here years before. – RobertS supports Monica Cellio Jun 30 '20 at 16:02
  • @RobertSsupportsMonicaCellio Thanks. Btw, I didn't mean to argue against *all* extern declarations (heaven forbid); it was meant in the context of a later or earlier static declaration. Clarified. – Peter - Reinstate Monica Jun 30 '20 at 16:11