Priority of static functions in C

Summary

Once you define a static function foo within a translation unit, foo refers to that function for the rest of the translation unit, except that it can be hidden by a non-function (such as an object or type definition) named foo for part of the translation unit. It will not link to an external function named foo.

By tinkering with declarations as explained below, an identifier could in theory refer to a function from another translation unit after a static declaration of the same name in this translation unit. Unfortunately, the behavior is not defined by the C standard, due to C 2018 6.2.2 7:

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

That means you cannot rely on the C standard alone to ensure this behavior, but a C implementation could define it as an extension.

Details

These questions are answered by C’s rules for scope and linkage.

Suppose in File1.c we have a static definition of a function:

static int foo(int x) { return x*x; }

Since the identifier foo is declared outside of any function, it has file scope (C 2018 6.2.1 4). This means the identifier foo is visible and designates this function definition for the remainder of File1.c. Also, since static was used, it has internal linkage (6.2.2 3).

There is an exception to the scope. For scopes inside other scopes, such as the block { … } that defines a function inside a file or a block inside a block, a declaration of the same identifier can hide the outer declaration. So let’s consider redeclaring foo inside a block.

In order to refer to a foo defined outside of File1.c, we would need to declare foo with external linkage, so that this new foo can be linked to the externally defined foo. Is there a way to do that in C?

If we try to declare extern int foo(int x); inside a block, then 6.2.2 4 applies:

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.

So this declaration would merely redeclare the same foo.

If we declare it without extern, using int foo(int x);, 6.2.2 5 applies:

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, it seems like we cannot declare a different foo with or without extern. But, wait, we have one more trick. We can make the prior declaration that specifies internal or external linkage invisible by hiding it with a declaration with no linkage. To get a declaration with no linkage, we can declare an object (rather than a function) without extern:

#include <stdio.h>

static int foo(int x) { return x*x; }

void bar(void)
{
    int foo; // Not used except to hide the function foo.
    {
        extern int foo(int x);
        printf("%d\n", foo(3));
    }
}

Since, where extern int foo(int x); appears, the prior declaration of foo with internal linkage is not visible, that first condition in 6.2.2 4 quoted above does not apply, and the remainder of 6.2.2 4 does:

If no prior declaration is visible, or if the prior declaration specifies no linkage, then the identifier has external linkage.

This is “legal” C code. Unfortunately, it is undefined by 6.2.2 7:

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


does that mean that internal calling of Foo function in File1.c is always resolved during compilation?

Not necessarily. For the most part, the language itself does not care HOW its rules are enforced, just that they are enforced in the first place.

We can check how a given toolchain (in my case linux/gcc) chooses to do it with a quick test:

Starting from a a simple file (test.c):

#include <stdio.h>

static void foo() {
    printf("hello");
}

void bar() {
    foo();
}

And then compile and inspect the resulting object file:

gcc -c -o test.o test.cpp
nm test.o

0000000000000018 T bar
0000000000000000 t foo
                 U _GLOBAL_OFFSET_TABLE_
                 U printf

We see that both foo() and bar() are in the symbol table, but with different flags.

We can also look at the assembly:

objdump -d test.o

0000000000000018 <bar>:
  18:   55                      push   %rbp
  19:   48 89 e5                mov    %rsp,%rbp
  1c:   b8 00 00 00 00          mov    $0x0,%eax
  21:   e8 da ff ff ff          callq  0 <foo>
  26:   90                      nop
  27:   5d                      pop    %rbp
  28:   c3                      retq  

And see that the call to foo is yet unlinked (pointing to the 0 placeholder). So we can say with confidence that the resolving can and does happen at link time in this instance.

Are there cases where Foo calling in File1.c can be linked to the global Foo function of File2.c?

That's a straight no. It might be possible to make that happen through hacky magic or undefined behavior, but in a normal project, you should be confident it never happens.