最小可运行多文件范围示例
在这里,我说明了如何static
影响跨多个文件的函数定义范围。
交流
#include <stdio.h>
/* Undefined behavior: already defined in main.
* Binutils 2.24 gives an error and refuses to link.
* https://stackoverflow.com/questions/27667277/why-does-borland-compile-with-multiple-definitions-of-same-object-in-different-c
*/
/*void f() { puts("a f"); }*/
/* OK: only declared, not defined. Will use the one in main. */
void f(void);
/* OK: only visible to this file. */
static void sf() { puts("a sf"); }
void a() {
f();
sf();
}
主程序
#include <stdio.h>
void a(void);
void f() { puts("main f"); }
static void sf() { puts("main sf"); }
void m() {
f();
sf();
}
int main() {
m();
a();
return 0;
}
GitHub 上游.
编译并运行:
gcc -c a.c -o a.o
gcc -c main.c -o main.o
gcc -o main main.o a.o
./main
输出:
main f
main sf
main f
a sf
解释
- 有两个独立的函数
sf
,每个文件一个
- 有一个共享功能
f
像往常一样,范围越小越好,所以static
如果可以的话,总是声明函数。
在 C 编程中,文件经常被用来表示“类”,static
函数表示类的“私有”方法。
一个常见的 C 模式是传递一个this
结构体作为第一个“方法”参数,这基本上是 C++ 在幕后所做的。
关于它的标准是什么
C99 N1256 草案6.7.1“存储类说明符”说这static
是一个“存储类说明符”。
6.2.2/3“标识符的链接”说static
暗示internal linkage
:
如果对象或函数的文件范围标识符的声明包含存储类说明符 static,则该标识符具有内部链接。
并且 6.2.2/2 表示其internal linkage
行为类似于我们的示例:
在构成整个程序的一组翻译单元和库中,具有外部链接的特定标识符的每个声明都表示相同的对象或函数。在一个翻译单元中,具有内部链接的标识符的每个声明都表示相同的对象或函数。
其中“翻译单元”是预处理后的源文件。
GCC 如何为 ELF (Linux) 实现它?
与STB_LOCAL
绑定。
如果我们编译:
int f() { return 0; }
static int sf() { return 0; }
并使用以下命令反汇编符号表:
readelf -s main.o
输出包含:
Num: Value Size Type Bind Vis Ndx Name
5: 000000000000000b 11 FUNC LOCAL DEFAULT 1 sf
9: 0000000000000000 11 FUNC GLOBAL DEFAULT 1 f
所以绑定是它们之间唯一的显着区别。Value
只是它们在该.bss
部分中的偏移量,因此我们希望它会有所不同。
STB_LOCAL
记录在http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html的 ELF 规范中:
STB_LOCAL 局部符号在包含其定义的目标文件之外不可见。同名的本地符号可以存在于多个文件中,互不干扰
这使它成为代表的完美选择static
。
没有 static 的函数是STB_GLOBAL
,并且规范说:
当链接编辑器组合几个可重定位的目标文件时,它不允许同名的 STB_GLOBAL 符号的多个定义。
这与多个非静态定义上的链接错误一致。
如果我们用 来加速优化-O3
,该sf
符号将从符号表中完全删除:无论如何它不能从外部使用。TODO 为什么在没有优化的情况下将静态函数保留在符号表上?它们可以用于任何事情吗?
也可以看看
C++ 匿名命名空间
在 C++ 中,您可能希望使用匿名命名空间而不是静态,这可以达到类似的效果,但进一步隐藏了类型定义:未命名/匿名命名空间与静态函数