问题引入
首先是两个测试程序
// foo.c
long long int a;
// bar.c
#include <stdio.h>
int a;
int main(){
a = 1;
long long int len = sizeof(a);
printf("%lld\n", len);
return 0;
}
将两个程序链接到一起
问题:len等于几?
初步分析
环境:两个程序均为C程序,当前GCC版本为7.5.0,Ubuntu18下
foo.c
和bar.c
中a
均为弱符号(foo.c
和bar.c
中a
均为未初始化的全局变量)- 根据多重定义符号的处理规则
-
可能任选一个
(袁春风《计算机系统基础》第二版176页,CSAPP第三版471页)
尝试不同链接顺序:预计输出一个4,一个8
-gcc foo.c bar.c -o foo_bar
-gcc bar.c foo.c -o bar_foo
运行,输出都是4(???) -
也可能选占用空间最大的一个:应该是8(???)
(《程序员的自我修养》92页)
-
逐步分析
-
首先分开编译:
gcc -c bar.c
gcc -c foo.c
-
查看目标文件的符号表:
-
readelf -s bar.o
:a大小为4
-
readelf -s foo.o
:a大小为8
分析:sizeof(a)在编译期间就被处理完了,
bar.c
中len
可以被编译优化为4 -
-
然后进行链接,与链接顺序无关,所以最终应该输出4
gcc bar.o foo.o bar_foo
gcc foo.o bar.o foo_bar
-
反汇编看一下:
objdump -d bar_foo
-
再多看一步,看一下符号表:
readelf -s bar_foo
注意到a占用了8个字节,但是最终输出sizeof(a)=4
,因为一个在链接阶段进行处理,一个在编译阶段进行处理,这也说明了如果有多个弱符号,最终选个最大的(?)
再分析
将两个程序变一下,重命名为foo.cpp
,bar.cpp
,编译运行一下
直接链接报错:
再逐步看一下
-
分开编译,看一下符号表
前面a在COMMOM块中,现在a在放在了bss节中(foo.cpp
相同)
-
强行将变量放到COMMON块中,修改
bar.cpp
-
再编译,看一下符号表
没问题,之后正常输出4
小结
- 如果是C++程序,对于未初始化的全局变量(称为tentative definition),放在bss节中(相当于编译器默认初始化为0)
- 如果非要放在common块中,需要在变量前设置属性
__attribute__((common))
,设置gcc编译选项没用
- 如果非要放在common块中,需要在变量前设置属性
- 如果是C程序,对于未初始化的全局变量,编译时gcc选项:
-fcommon
:未初始化的全局变量放在common块中(GCC 9之前默认)-fno-common
:未初始化的全局变量放在bss节中(GCC 10之后默认)
参考
为什么访问外部变量不需要extern也可以?
《程序员的自我修养——链接、装载与库》3.5.5节,4.3节
《计算机系统基础》第二版 4.3.2节