最小可运行的多文件作用域示例
在这里,我将说明静态如何跨多个文件影响函数定义的作用域。
a.c
#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();
}
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
通常,作用域越小越好,所以如果可以,总是将函数声明为静态的。
在C语言编程中,文件通常用来表示“类”,静态函数表示类的“私有”方法。
一个常见的C模式是将this结构体作为第一个“方法”参数传递,这基本上就是c++在底层所做的事情。
这是什么标准啊
C99 N1256草案6.7.1“存储类说明符”说static是一个“存储类说明符”。
6.2.2/3“标识符的关联”说静态意味着内部关联:
如果对象或函数的文件作用域标识符声明中包含存储类说明符static,则该标识符具有内部链接。
6.2.2/2表示内部链接的行为就像我们的例子:
在构成整个程序的翻译单元和库的集合中,带有外部链接的特定标识符的每个声明都表示相同的对象或函数。在一个翻译单元中,带有内部链接的标识符的每个声明都表示相同的对象或函数。
其中“翻译单元”是预处理后的源文件。
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在ELF规范http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html:上有文档
局部符号在包含其定义的目标文件之外是不可见的。相同名称的局部符号可以存在于多个文件中而互不干扰
这使得它成为代表静态的完美选择。
不带static的函数是STB_GLOBAL,规范说:
当链接编辑器组合了几个可重定位的目标文件时,它不允许使用相同名称定义多个STB_GLOBAL符号。
这与多个非静态定义的链接错误是一致的。
如果我们使用-O3进行优化,sf符号将完全从符号表中删除:无论如何它都不能从外部使用。当没有优化时,为什么要在符号表上保持静态函数?它们有什么用途吗?
另请参阅
变量也是一样:https://stackoverflow.com/a/14339047/895245
extern是静态的反面,函数默认情况下已经是extern:我如何使用extern在源文件之间共享变量?
C+匿名namespaces
在c++中,您可能希望使用匿名名称空间而不是静态名称空间,这可以达到类似的效果,但进一步隐藏了类型定义:未命名/匿名名称空间vs.静态函数