最小的可运行多文件作用域示例
在这里,我说明了如何static
影响多个文件中函数定义的范围。
交流电
#include <stdio.h>
/* Undefined behavior: already defined in main.
* Binutils 2.24 gives an error and refuses to link.
* /programming/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();
}
main.c
#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
:
如果对象或函数的文件作用域标识符的声明包含静态的存储类说明符,则该标识符具有内部链接。
和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 ++中,您可能想使用匿名名称空间而不是静态名称空间,这可以达到类似的效果,但是会进一步隐藏类型定义:未命名/匿名名称空间与静态函数