13.2 C语言程序的段
13.2.1 段的分类
根据C语言的特点,每一个源程序生成的目标代码将包含源程序所需要表达的所有信息和功能。目标代码中各段生成情况如下:
1.代码段(Code)
代码段由程序中的各个函数产生,函数的每一个语句将最终经过编译和汇编生成二进制机器代码(具体生成哪种体系结构的机器代码由编译器决定)。
顺序代码
基本数学运算(+,-),逻辑运算(&&,||),位运算(&,|,^)等都属于顺序代码。
选择代码
if,if…else语句等将由编译器生成选择代码。
循环代码
while(),do…while()语句等将由编译器生成循环代码。
对于一些较为复杂的数学运算如除法(\),取余(%)等,虽然它们是C语言的基本运算,但在各种编译系统中的处理方式却不一定相同。根据编译器和体系结构的特点,对它们的处理方式有可能与加减等运算相同,即直接生成处理器的机器代码,也有可能转换成一个库函数的调用。例如,在没有除法指令的体系结构中,编译器在编译a/b这类除法运算的时候,由于处理器没有与其对应的指令,因此会使用调用库函数来模拟除法运算。浮点数的处理与之类似:对于支持浮点运算的体系结构,将直接生成浮点代码;对于不支持浮点数的处理器,编译器将会把每一个浮点运算用库函数调用的方式模拟。
2.只读数据段(RO Data)
只读数据段由程序中所使用的数据产生,该部分数据的特点是在运行中不需要改变,因此编译器会将该数据放入只读的部分中。C语言的一些语法将生成只读数据段。
只读全局量
例如:定义全局变量const char a[100]={"ABCDEFG"}将生成大小为100个字节的只读数据区,并使用字串"ABCDEFG"初始化。如果定义为const char a[]={"ABCDEFG"},没有指定大小,将根据"ABCDEFG"字串的长度,生成8个字节的只读数据段。
只读局部量
例如:在函数内部定义的变量const char b[100] ={"9876543210"};其初始化的过程和全局量一样。
程序中使用的常量
例如:在程序中使用printf("information \n"),其中包含了字串常量,编译器会自动把常量"information \n"放入只读数据区。
在const char a[100]={"ABCDEFG"}中,定义了100个字节的数据区,但是只初始化了前面的8个字节(7个字符和表示结束的'\0')。在这种用法中,实际后面的字节没有初始化,但是在程序中也不能写,实际上没有任何用处。因此,在只读数据段中,一般都需要做完全的初始化。
3.读写数据段(RW Data)
读写数据段表示了在目标文件中一部分可以读也可以写的数据区,在某些场合它们又被称为已初始化数据段。这部分数据段和代码段,与只读数据段一样都属于程序中的静态区域,但是具有可写的特点。
已初始化全局静态变量
例如:在函数外部,定义全局的变量char a[100]={"ABCDEFG"}
已初始化局部静态变量
例如:在函数中定义static char b[100] ={"9876543210"}。函数中由static定义并且已经初始化的数据和数组将被编译为读写数据段。
读写数据区的特点是必须在程序中经过初始化,如果只有定义,没有初始值,则不会生成读写数据区,而会定位为未初始化数据区(BSS)。如果全局变量(函数外部定义的变量)加入static修饰符,写为类似static char a[100]的形式,这表示只能在文件内使用,而不能被其他文件使用。
4.未初始化数据段(BSS)
未初始化数据段常被称之为BSS(英文Block Start by Symbol的缩写)。与读写数据段类似,它也属于静态数据区,但是该段中的数据没有经过初始化。因此它只会在目标文件中被标识,而不会真正称为目标文件中的一个段,该段将会在运行时产生。未初始化数据段只有在运行的初始化阶段才会产生,因此它的大小不会影响目标文件的大小。
在C语言的程序中,对变量的使用还有以下几点需注意:
1.在函数体中定义的变量通常是在栈上,不需要在程序中进行管理,由编译器处理。
2.用malloc,calloc,realloc等分配内存的函数所分配的内存空间在堆上,程序必须保证在使用后用free释放,否则会发生内存泄漏。
3.所有函数体外定义的是全局变量,加了static修饰符后的变量不管在函数内部或者外部都存放在全局区(静态区)。
4.使用const定义的变量将放于程序的只读数据区。
在C语言中,可以定义static变量:在函数体内定义的static变量只能在该函数体内有效;在所有函数体外定义的static变量,也只能在该文件中有效,不能在其他的源文件中使用;对于没有使用static修饰的全局变量,可以在其他的源文件中使用。这些区别是编译的概念,即如果不按要求使用变量,编译器会报错。使用static和没有使用static修饰的全局变量最终都将放置在程序的全局区(静态区)。
原文地址:http://book.51cto.com/art/200902/111809.htm
3.2.2 程序中段的使用
本小节使用简单的例子,说明C语言中变量和段的对应关系。C语言程序中的全局区(静态区),实际对应着下述几个段:
只读数据段:RO Data
读写数据段:RW Data
未初始化数据段:BSS Data
一般来说,直接定义的全局变量在未初始化数据区,如果该变量有初始化则是在已初始化数据区(RW Data),加上const修饰符将放置在只读区域(RO Data)。
示例1:
const char ro[]={"this is readonly data"}; /* 只读数据段 */ static char rw1[]={"this is global readwrite data"}; /* 已初始化读写数据段 */ char bss_1[100]; /* 未初始化数据段 */ const char* ptrconst = "constant data"; /* "constant data"放在只读数据段 */ int main() { short b; /* b放置在栈上,占用2个字节 */ char a[100]; /* 需要在栈上开辟100个字节,a的值是其首地址 */ char s[] = "abcde"; /* s在栈上,占用4个字节 */ /* "abcde "本身放置在只读数据存储区,占6字节 */ char *p1; /* p1在栈上,占用4个字节 */ char *p2 = "123456"; /* "123456"放置在只读数据存储区,占7字节 */ /* p2在栈上,p2指向的内容不能更改。 */ static char rw2[]={"this is local readwrite data"}; /* 局部已初始化读写数据段 */ static char bss_2[100]; /* 局部未初始化数据段 */ static int c = 0; /* 全局(静态)初始化区 */ p1= (char *)malloc(10*sizeof(char)); /* 分配的内存区域在堆区。 */ strcpy(p1, "xxxx"); /* "xxxx"放置在只读数据存储区,占5字节 */ free(p1); /* 使用free释放p1所指向的内存 */ return 0; } |
示例1程序中描述了C语言源文件中语句如何转换成各个段。
只读数据段需要包括程序中定义的const型的数据(如:const char ro[]),还包括程序中需要使用的数据如"123456"。对于const char ro[]和const char* ptrconst的定义,它们指向的内存都位于只读数据区,其指向的内容都不允许修改。区别在于前者不允许在程序中修改ro的值,后者允许在程序中修改ptrconst本身的值。对于后者,改写成以下的形式,将不允许在程序中修改ptrconst本身的值:
const char* const ptrconst = "constant data"; |
static char rw1[100]={"that is global readwrite data"}; static char rw2[200]={"that is local readwrite data"}; |
则读写数据区域的大小直接由后面的数值(100或者200)决定,超出字符串长度的空间的内容为0。
未初始化数据段,示例1中的bss_1[100]和bss_2[200]在程序中代表未初始化的数据段。其区别在于前者是全局的变量,在所有文件中都可以使用;后者是局部的变量,只在函数内部使用。未初始化数据段不设置后面的初始化数值,因此必须使用数值指定区域的大小,编译器将根据大小设置BBS中需要增加的长度。
栈空间包括函数中内部使用的变量如short b和char a[100],以及char *p1中p1这个变量的值。
变量p1指向的内存建立在堆空间上,栈空间只能在程序内部使用,但是堆空间(例如p1指向的内存)可以作为返回值传递给其他函数处理。示例1中各段的使用如表13-1所示。
表13-1 示例1中各段的使用
段 | 使用情况 | 列 表 |
只读数据(RO data) | 程序中不需要更改的全局变量 | const char ro[]={"it is readonly data"}; char s[] = "abcde";中的"abcde",占用6字节 char *p2 = "123456";中的"123456",占用7字节 strcpy(p1, "xxxx");中的"xxxx",占用5字节 |
读写数据(RW data) | 程序中需要更改,但是具有初始化值的全局变量 | static char rw1[]={"that is global readwrite data"}; static char rw2[]={"that is local readwrite data"}; |
未初始化数据(BSS) | 没有初始化值的全局变量 | char bss_1[100]; char bss_2[100]; |
堆(heap) | 需要用户分配的空间 | p1= (char *)malloc(10*sizeof(char)); p1指向的内存空间 |
栈(stack) | 函数中临时变量 函数入口和返回值 | 函数中的变量: short b; char a[100]; char *p1; 变量p1本身占用4字节 |
示例2:
long fun(long c) { char a[100]; /* 栈上的100个字节 */ long b; /* 栈上的4个字节 */ /* ………… */ return b; } |
栈空间主要用于以下3数据的存储:
函数内部的动态变量
函数的参数
函数的返回值
栈空间主要的用处是供函数内部的动态变量使用,变量的空间在函数开始之前开辟,在函数退出后由编译器自动回收。
栈空间的另外一个作用是在程序进行函数调用的时候进行函数的参数传递以及保存函数返回值。
在示例2程序的调用过程中,需要使用栈保存临时变量。在调用之前,需要将变量C保存在堆栈上,占用4字节,在函数返回之前,需要将返回值b保存在栈上,也占用4个字节的空间。同时,为数组a开辟100字节的栈空间。
在函数的调用过程中,如果函数调用的层次比较多,所需要的栈空间也逐渐增大。对于参数的传递和返回值,如果使用较大的结构体,在使用的栈空间也会比较大。