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 函数的情况?
这是直接的否定。也许可以通过黑客魔法或未定义的行为来实现这一点,但在正常的项目中,您应该确信它永远不会发生。