C语言中静态函数的优先级

Juj*_*uke 8 c static function linkage

假设您有一个带有静态函数Foo的File1.c文件,并且在File1.c中调用了该函数。此外,在另一个文件(File2.c)中,您还有另一个非静态的Foo函数。我知道静态函数在声明它的文件外部不可见,而对于链接器实际上是不可见的。

但这是否意味着在编译过程中始终可以解析File1.c中Foo函数的内部调用?

在某些情况下,可以将File1.c中的Foo调用链接到File2.c的全局Foo函数吗?

Eri*_*hil 5

摘要

foo在翻译单元中定义静态函数后foo,请为该翻译单元的其余部分引用该函数,但该函数可以被foo为该翻译单元的一部分命名的非函数(例如对象或类型定义)隐藏。它不会链接到名为的外部函数foo。

通过如下所述修改声明,标识符在理论上可以在另一个翻译单元static中的同名声明之后引用来自另一个翻译单元的函数。不幸的是,由于C 2018 6.2.2 7,该行为未由C标准定义:

如果在翻译单元中,具有内部和外部链接的同一标识符出现,则行为未定义。

这意味着您不能仅依靠C标准来确保这种行为,但是C实现可以将其定义为扩展。

细节

C的范围和链接规则回答了这些问题。

假设File1.c我们有一个静态的函数定义:

static int foo(int x) { return x*x; }
Run Code Online (Sandbox Code Playgroud)

由于标识符foo是在任何函数之外声明的,因此它具有文件范围(C 2018 6.2.1 4)。这意味着标识符foo是可见的,并为的其余部分指定此函数定义File1.c。同样,由于static使用了它,所以它具有内部链接(6.2.2 3)。

范围有一个例外。对于其他作用域内的作用域,例如{ … }在文件内定义函数的块或在块内定义块的块,具有相同标识符的声明可以隐藏外部声明。因此,让我们考虑foo在块内重新声明。

为了引用foo外部定义File1.c,我们需要foo使用外部链接进行声明,以便可以将此新foo链接链接到外部定义foo。有没有办法在C中做到这一点?

如果我们尝试extern int foo(int x);在块内声明,则适用6.2.2 4:

对于extern在可见该标识符的先前声明的范围内用存储类说明符声明的标识符,如果该先前声明指定了内部或外部链接,则该标识符在后面的声明中的链接与指定的链接相同在事先声明。

因此,此声明只会重新声明相同的内容foo。

如果我们声明不带extern,则使用int foo(int x);,则适用6.2.2 5:

如果函数标识符的声明没有存储类说明符,则将其链接确定为与使用存储类说明符声明的完全相同extern。

因此,似乎我们不能使用或不使用来声明其他foo extern。但是,等等,我们还有另一个窍门。通过使用没有链接的声明将其隐藏,可以使指定内部或外部链接的先前声明不可见。为了获得没有链接的声明,我们可以声明一个对象(而不是一个函数),而无需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));
    }
}
Run Code Online (Sandbox Code Playgroud)

由于看不到带有内部链接extern int foo(int x);的在先声明foo,因此上面引用的6.2.2 4中的第一个条件不适用,而6.2.2 4的其余部分则适用:

如果没有在先声明可见,或者在先声明没有指定链接,则标识符具有外部链接。

这是“合法的” C代码。不幸的是,6.2.2 7没有定义它:

如果在翻译单元中,具有内部和外部链接的同一标识符出现,则行为未定义。


小智 4

这是否意味着 File1.c 中 Foo 函数的内部调用始终在编译期间解析?

不必要。在大多数情况下,语言本身并不关心其规则如何执行,而只关心它们首先被执行。

我们可以通过快速测试来检查给定的工具链(在我的例子中是 linux/gcc)如何选择执行此操作:

从一个简单的文件(test.c)开始:

#include <stdio.h>

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

void bar() {
    foo();
}
Run Code Online (Sandbox Code Playgroud)

然后编译并检查生成的目标文件:

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

0000000000000018 T bar
0000000000000000 t foo
                 U _GLOBAL_OFFSET_TABLE_
                 U printf
Run Code Online (Sandbox Code Playgroud)

我们看到 和foo()都bar()在符号表中,但具有不同的标志。

我们还可以看一下装配:

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  
Run Code Online (Sandbox Code Playgroud)

并看到对 foo 的调用尚未链接(指向 0 占位符)。因此,我们可以自信地说,在这种情况下,解析可以而且确实在链接时发生。

是否存在 File1.c 中的 Foo 调用可以链接到 File2.c 的全局 Foo 函数的情况?

这是直接的否定。也许可以通过黑客魔法或未定义的行为来实现这一点,但在正常的项目中,您应该确信它永远不会发生。