Inconsistent storage class specifiers on functions

Regarding:

static void static_func(void);
void static_func(void) { }

For the first line, C 2018 6.2.2 3 says:

If the declaration of a file scope identifier for an object or a function contains the storage-class specifier static, the identifier has internal linkage.

For the second line, 6.2.2 5 says:

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

so we refer to the paragraph about when extern is specified, 6.2.2 4 (emphasis added):

For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible, 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.…

Thus, since the prior declaration is visible, void static_funct(void) is equivalent to static void static_funct(void).

(Note that 6.2.2 5 differs for objects; after the portion quoted above, it goes on to say “If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.” Thus static int x; int x; creates a conflict for the object x, whereas static int f(void); int f(void); does not create a conflict for the function f.)

Regarding:

static inline void static_inline_func(void);
void static_inline_func(void) { }

inline is a function specifier, not a storage-class specifier, and 6.7.4 6 tells us:

A function declared with an inline function specifier is an inline function.…

As we saw above, void static_inline_func(void) { } still has internal linkage, due to the prior declaration. 6.7.4 7 is quite lax about the requirements on inline functions with internal linkage:

Any function with internal linkage can be an inline function.…

Had the function not be declared with static, there are further restrictions in 6.7.4 7:

For a function with external linkage, the following restrictions apply: If a function is declared with an inline function specifier, then it shall also be defined in the same translation unit. If all of the file scope declarations for a function in a translation unit include the inline function specifier without extern, then the definition in that translation unit is an inline definition. An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit. An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit.…

(It is not clear where the text covered by “the following restrictions apply” ends.)

Thus, there may be both an inline definition of a function and an external definition (in another translation unit). In either case, there do not appear to be any prohibitions on declaring a function both with and without inline; there are merely implications to doing so, notably that declaring an external function both with and without inline means the definition in that translation unit is not an inline definition.


I'm pretty sure this is valid C.

The following standard references are from C99, but the C17 draft contains exactly the same text.

6.2.2 (Linkages of identifiers) says:

  1. For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible, 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.

  2. 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.

Thus: extern doesn't always mean external linkage. If there is an existing declaration with internal linkage (static) visible in the current scope, that's what takes precedence.

However, 6.11 (Future language directions) also says:

6.11.2 Linkages of identifiers

  1. Declaring an identifier with internal linkage at file scope without the static storage-class specifier is an obsolescent feature.

So while this is a valid feature of C, you probably shouldn't rely on it in new code.


Quote from 6.2.2 Linkagesof identifiers p4.

For an identifier declared with the storage-class specifier extern in a scope in which prior declaration of that identifier is visible,23)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

So, in these 2 cases the 2nd declaration is correct, and the linkage is static (the 2nd declaration is external by default):

static void static_func(void);
void static_func(void) { }

static inline void static_inline_func(void);
void static_inline_func(void) { }

Quote from p7, same chapter:

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

So, declaring it like this is undefined behavior.

#if 0 //error
    void extern_func(void){}
    static void extern_func(void);
#endif