Linux下C程序的存储空间布局

Posted qiuri2008

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux下C程序的存储空间布局相关的知识,希望对你有一定的参考价值。

在ELF格式的可执行文件中,全局内存包括三种:bss、data和rodata。其它可执行文件格式与之类似。了解了这三种数据的特点,我们才能充分发挥它们的长处,达到速度与空间的最优化。
1、bss(Block Started by Symbol)
    bss是指那些没有初始化的和初始化为0的全局变量和静态变量,bss类型的全局变量只占运行时的内存空间,而不占文件空间。
    另外,大多数操作系统,在加载程序时,会把所有的bss全局变量全部清零,无需要你手工去清零。
    但为保证程序的可移植性,手工把这些变量初始化为0也是一个好习惯。
2、data
    与bss相比,data就容易明白多了,它的名字就暗示着里面存放着数据。当然,如果数据全是零,为了优化考虑,编译器把它当作bss处理。通俗的说,data指那些初始化过(非零)的非const的全局变量和静态变量。
    由此可见,data类型的全局变量是即占文件空间,又占用运行时内存空间的。
3、rodata
    rodata的意义同样明显,ro代表read only,即只读数据(const)。只读数据段,存放常量,字符常量,const常量,据说还存放调试信息。关于rodata类型的数据,要注意以下几点:
    常量不一定就放在rodata里,有的立即数直接编码在指令里,存放在代码段(.text)中。
    对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件(EXE/SO)中只存在一份拷贝。
    rodata是在多个进程间是共享的,这可以提高空间利用率。
    在有的嵌入式系统中,rodata放在ROM(如norflash)里,运行时直接读取ROM内存,无需要加载到RAM内存中。
    在嵌入式linux系统中,通过一种叫作XIP(就地执行)的技术,也可以直接读取,而无需要加载到RAM内存中。
    由此可见,把在运行过程中不会改变的数据设为rodata类型的,是有很多好处的:在多个进程间共享,可以大大提高空间利用率,甚至不占用RAM空间。同时由于rodata在只读的内存页面(page)中,是受保护的,任何试图对它的修改都会被及时发现,这可以帮助提高程序的稳定性。
4、text
     通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
5、变量与关键字
    static关键字用途太多,以致于让新手模糊。不过,总结起来就有两种作用,改变生命期和限制作用域。如:
    修饰inline函数:限制作用域
    修饰普通函数:限制作用域
    修饰局部变量:改变生命期
    修饰全局变量:限制作用域
    const 关键字倒是比较明了,用const修饰的变量放在rodata里,字符串默认就是常量。对const,注意以下几点就行了。
    指针常量:指向的数据是常量。如 const char* p = “abc”; p指向的内容是常量 ,但p本身不是常量,你可以让p再指向”123”。
    常量指针:指针本身是常量。如:char* const p = “abc”; p本身就是常量,你不能让p再指向”123”。
    指针常量 + 常量指针:指针和指针指向的数据都是常量。const char* const p =”abc”; 两者都是常量,不能再修改。
    violatile关键字通常用来修饰多线程共享的全局变量和IO内存。告诉编译器,不要把此类变量优化到寄存器中,每次都要老老实实的从内存中读取,因为它们随时都可能变化。这个关键字可能比较生僻,但千万不要忘了它,否则一个错误让你调试好几天也得不到一点线索。

一个程序本质上都是由 BSS 段、data段、text段三个组成的。可以看到一个可执行程序在存储(没有调入内存)时分为代码段、数据区和未初始化数据区三部分。

  • BSS段(未初始化数据区):在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
  • 数据段:在采用段式内存管理的架构中,数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
  • 代码段:在采用段式内存管理的架构中,代码段(text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

程序编译后生成的目标文件至少含有这三个段,这三个段的大致结构图如下所示:

text段和data段在编译时已经分配了空间,而BSS段并不占用可执行文件的大小,它是由链接器来获取内存的
    bss段(未进行初始化的数据)的内容并不存放在磁盘上的程序文件中。其原因是内核在程序开始运行前将它们设置为0。需要存放在程序文件中的只有正文段和初始化数据段。
    data段(已经初始化的数据)则为数据分配空间,数据保存到目标文件中。

数据段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到,然后链接器得到这个大小的内存块,紧跟在数据段的后面。当这个内存进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。

    可执行程序在运行时又多出两个区域:栈区和堆区。
    (4)栈区:由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的 函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容 量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。
    (5)堆区:用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的 malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。

下图将体现c的源文件对应存储空间:

此时程序还没有被放入内存,只是在硬盘存储的情况,此时bss并未占用空间。bss在链接的时候被获得内存空间。

下图表示程序运行,即程序在内存时的存储布局:

 

 

 

复制代码
//main.c  
int a = 0; //全局初始化区  
char *p1; //全局未初始化区  
  
main()  
{  
    static int c =0; //全局(静态)初始化区  
    int b; //栈  
    char s[] = "abc"; //栈  
    char *p2; //栈  
    char *p3 = "123456"; //"123456\\0"在常量区,p3在栈上。  
    p1 = (char *)malloc(10);  
    p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。  
}
复制代码

 

以上是关于Linux下C程序的存储空间布局的主要内容,如果未能解决你的问题,请参考以下文章

Linux下C程序的存储空间布局

Linux C程序存储空间的逻辑布局

Linux下C程序的内存布局

Linux下C程序的内存布局

存储类生命周期作用域链接域

Linux进程地址空间与进程内存布局详解,内核空间与用户空间