最小化可运行的多文件作用域示例
这里我演示了如何使用 static
跨多个文件定义函数作用域的影响。
a.c
#include <stdio.h>
void f(void);
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 upstream。
编译并运行:
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
意味着 内部链接
:
如果一个对象或者函数的文件范围标识符的声明包含存储类说明符 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:
STB_LOCAL本地符号在包含其定义的对象文件外不可见。具有相同名称的局部符号可能存在于多个文件中,而彼此不会干扰
这使得它成为表示static
的完美选择。
没有静态函数的函数是STB_GLOBAL
,规范如下:
当链接编辑器组合多个可重定位目标文件时,它不允许具有相同名称的STB_GLOBAL符号的多个定义。
这与多个非静态定义的链接错误一致。
如果我们使用-O3
来提高优化级别,则sf
符号将完全从符号表中删除:无论如何,它都不能从外部使用。 TODO在没有优化时为什么仍要将静态函数保留在符号表中?它们可以用于什么?
另请参阅
C++匿名名称空间
在C ++中,您可能希望使用匿名命名空间而不是静态命名空间,这具有类似的效果,但进一步隐藏了类型定义:Unnamed/anonymous namespaces vs. static functions