段
在绝大多数SVr4实现中都采用了一种称作ELF(原意为Extensible Linker Format[可扩展链接器格式],现在表示Executable and Linking Format[可执行文件和链接格式]的格式,在其他系统中,可执行文件的格式是COFF(Common Object-FILE Format,普通目标文件格式)。在BSD UNIX中,a.out文件具有a.out格式。可以通过输入man a.out在主文档中查看有关UNIX系统所使用的格式的信息。
所有不同的格式具有一个共同的概念,那就是段(segment)。但就目标文件而言,它们是二进制文件中简单的区域,里面保存了和某种特定类型(如符号表条目)相关的所有信息。术语section也被广泛试用,section是ELF文件中的最小组织单位。一个段一般包括几个section.
在UNIX中,段表示一个二进制文件相关的内容块。
在Intel x86的内存模型中,段表示一个设计的结果。在这种设计中(基于兼容性原因),地址空间并非一个整体,而是分成一些64KB大小的区域,称之为段。
在本书的剩余部分,如果不做特殊说明,段这个术语是指UNIX上的段。
当在一个可执行文件中运行size命令时,它会告诉你这个文件中的3个段(文本段、数据段和bss段)的大小:
%echo; echo "text data bss total"; size a.out
size命令并不打印标题,所以要用echo命令产生它们。
检查可执行文件的内容的另一种方法是使用nm或dump实用工具。编译下面的源文件,在结果的a.out文件上运行nm程序。
char pear[40];
static double peach;
int mango = 13;
static long melon = 2001;
int main() {
int i = 3, j, *ip;
ip = malloc(sizeof(i));
pear[5] = i;
peach = 2.0 * mango;
return 0;
}
nm程序运行结果的摘要如下
%nm -sx a.out
Symbols from a.out
[Index] Value Size Type Bind Segment Name
OBJT LOCL .bss peach
OBJT GLOB .bss pear
OBJT GLOB .data mango
OBJT LOCL .data melon
FUNC GLOB .text main
FUNC GLOB UNDEF malloc
编译器和链接器分别在这些段中写入了什么东西
关键:
这个类型的C源文件---------------->(通过编译器编译)进入这个段
源文件 a.out文件
char pear[40];(OBJT GLOB BSS) (1) a.out神奇数字
static double peach(OBJT LOCL BSS)(2) a.out的其他内容
BSS段所需的大小(1)(2)
int mango = 13;(OBJT GLOB DATA) (3) 数据段
static long melon = 2001;(OBJT LOCL DATA)(4) 初始化后的全局变量和静态变量 (3)(4)
main() {(FUNC GLOB TEXT) 文本段
int i = 3, j, *ip(局部变量并不进入a.out,它们在运行时创建) 可执行文段的指令(5)(6)(7)
ip = malloc(sizeof(i));(FUNC GLOB UNDEF)(5)
pear[5] = i;(6)
peach = 20 * mango;(7)
}
图6-1 C语言的各部分会出现在那些段中
BSS段这个名字是“Block Started by Symbol”(由符号开始的块)的缩写,它是旧式IBM 704汇编程序的一个伪指令,UNIX借用了这个名字,至今依然沿用。有些人喜欢把它记做“Better Save Space”(更有效的节省空间)。运行时所需要的BSS段的大小记录在目标文件中,但BSS段(不像其他段)并不占据目标文件的任何空间。
/*
**编程挑战
*/
1.编译hello world程序,在可执行文件中执行ls -l,得到文件的总体大小。运行size得到文件里各个段的大小。
解析:
由于电脑是window系统,所以这里采用dir执行UNIX中ls命令的功能。
/*program.c*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
printf( "%s\n", "hello, world!");
return EXIT_SUCCESS;
}
输出:
2.增加一个全局的int[1000]数组声明,重新进行编译,再用上面的命令得到总体及各个段的大小,注意前后的区别。
解析:
由于电脑是window系统,所以这里采用dir执行UNIX中ls命令的功能。
/* program_2.c */
#include <stdio.h>
#include <stdlib.h>
int a[1000];
int main( void ){
printf( "%s\n", "hello, world!");
return EXIT_SUCCESS;
}
输出:
3.现在,在数组的声明中增加初始值(记住,C语言并不强迫对数组进行初始化时为每个元素提供初始值),这将使数组从BSS段转换到数据段。重复上面的测量,注意各个段前后大小的区别。
解析:
由于电脑是window系统,所以这里采用dir执行UNIX中ls命令的功能。
/* program_3.c */
#include <stdio.h>
#include <stdlib.h>
int a[1000] = {1};
int main( void ){
printf( "%s\n", "hello, world!");
return EXIT_SUCCESS;
}
输出:
4.现在,在函数体内声明一个巨大的数组。然后再声明一个巨大的局部数组,但这次加上初始值。重复上面的测量。定义于函数内部的局部数组存储在可执行文件中吗?有没有初始化有什么不同吗?
解析:
由于电脑是window系统,所以这里采用dir执行UNIX中ls命令的功能。
/* program_4.c */
#include <stdio.h>
#include <stdlib.h>
int a[1000] = {1};
int main( void ){
int b[1000];
printf( "%s\n", "hello, world!");
return EXIT_SUCCESS;
}
输出:
/* program_5.c */
#include <stdio.h>
#include <stdlib.h>
int a[1000] = {1};
int main( void ){
int b[1000];
int c[1000] = {2};
printf( "%s\n", "hello, world!");
return EXIT_SUCCESS;
}
输出:
结果说明定义于函数内部的局部数组无论是否进行了初始化都没有存储于可执行文件中。
5.如果在调试状态下编译,文件和段的大小有没有变化?是为了最大限度的优化吗?
分析上面“编程挑战”的结果,使自己确信:
*数据段保存在目标文件中;
*BSS段不保存在目标文件中(除了记录BSS段在运行时所需要的大小);
*文本段是最容易受优化措施影响的段;
*a.out文件的大小受调试状态下编译的影响,但段不影响。
/* program.c */
#include <stdio.h>
#include <stdlib.h>
int main( void ){
printf( "%s\n", "hello, world!");
return EXIT_SUCCESS;
}
输出:
/* program_6.c */
#include <stdio.h>
#include <stdlib.h>
int a[1000];
int a2[1000] = {1};
int main( void ){
int b[1000];
int c[1000] = {2};
printf( "%s\n", "hello, world!");
return EXIT_SUCCESS;
}
输出:
我的操作系统是windows,这里保存了bss段和data段保存到了目标文件中,跟在UNIX系统中的结果不一样。
/* program_7.c */
#include <stdio.h>
#include <stdlib.h>
int a[1000];
int a2[1000] = {1};
int main( void ){
int b[1000];
int c[1000] = {2};
int i;
for( i = 0; i < 10; ++i ){
printf( "%d ", b[i] );
}
printf( "%s\n", "hello, world!");
return EXIT_SUCCESS;
}
输出: