本小节使用简单的例子,说明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[]以及局部静态变量static char rw2[]。rw1和rw2的差别只是在于编译时,是在函数内部使用的还是可以在整个文件中使用。对于前者,static修饰在于控制程序的其他文件是否可以访问rw1变量,如果有static修饰,将不能在其他的C语言原文件中使用rw1,这种影响针对编译-连接的特性,但无论有无static,变量rw1都将被放置在读写数据段。对于后者rw2,它是局部的静态变量,放置在读写数据区;如果不使用static修饰,其意义将完全改变,它将会是开辟在栈空间局部变量,而不是静态变量。在这里,rw1和rw2后面的方括号([])内没有数值,表示静态区的大小由后面字符串的长度决定,如果将它们的定义改写为以下形式:
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字节的栈空间。
在函数的调用过程中,如果函数调用的层次比较多,所需要的栈空间也逐渐增大。对于参数的传递和返回值,如果使用较大的结构体,在使用的栈空间也会比较大。