我想用c语言中的“size”命令来理解内存存储段。我分别做了这些:
file1.c
#include <stdio.h>
int main(void) {
return 0;
}
gcc -o size1 file1.c
size size1.exe
text data bss dec hex filename
7446 1404 244 12708 24b4 ./size1.exe
file2.c
#include <stdio.h>
int glb; /* Uninitialized variable stored in bss*/
int main(void) {
return 0;
}
gcc -o size2 file2.c
size size2.exe
text data bss dec hex filename
7446 1404 244 12708 24b4 ./size2.exe
在
file2.c
的大小中,我预计bss
段会增加,但没有改变。为什么会发生这种事?
添加了全局变量,但bss段没有增加
编辑:
* 使用目标文件而不是 .exe 解决了我的问题,但现在 .bss 增加了 16 而不是 4
问题演变为为什么.bss增加16而不是4?
通常的原因是“对齐”。程序中可能存在需要对齐到一定字节数的倍数的对象,这可能需要插入填充(已分配但未使用的字节)。新添加的对象可能碰巧放置在原本用于填充的区域中,在这种情况下,它实际上不会增加该部分的整体大小。
作为一个简单的示例,假设您的原始程序在
.bss
部分中有两个对象(例如,由启动代码使用),一个需要 2 字节对齐的 2 字节对象 my_short
,以及一个 8 字节对象 my_long
需要 8 字节对齐。链接器可能会碰巧将它们放置在内存中,如下所示:
0x0 - 0x1: my_short
0x2 - 0x7: (padding)
0x8 - 0xf: my_long
总共使用了16个字节,其中包括6个字节的填充。
现在添加一个新对象
glb
,4字节,需要4字节对齐。根据链接器处理其目标文件的顺序,它可以放置在 my_short
之后但在 my_long
之前。在这种情况下,内存映射可能如下所示:
0x0 - 0x1: my_short
0x2 - 0x3: (padding)
0x4 - 0x7: glb
0x8 - 0xf: my_long
总数仍然是 16 个字节,但现在只有 2 个字节的填充。
您可以使用
-Wl,-M
选项(这是一个带有逗号的选项,没有空格)为每个版本发出链接器映射,您应该能够看到它的发生。
在我的系统上,两个版本都有一个 8 字节的
.bss
部分。 ld
使用的链接器脚本(可以使用 -Wl,--verbose
打印)在 ALIGN
末尾包含一个 .bss
指令,因此 .bss
始终以 8 字节的倍数结束。 (脚本文件中有一条 FIXME 注释,表明其中一位程序员不确定他们为什么这样做,或者是否真的有必要。)
除了
glb
之外,.bss
中只有 1 个字节(由启动代码使用),因此后面要么是 7 个字节的填充,要么是 3 个字节的填充,后跟 glb
。