C语言内存模型 (C memory layout)

 一. 内存模型                                                                                                                                    

1. .text

代码区(code section)。由编译器链接器生成的可执行指令,程序执行时由加载器(loader)从可执行文件拷贝到内存中。为了安全考虑,防止别的区域更改代码区数据(即可执行指令),代码区具有只读属性。另一个方面,代码区通常具有可共享性(sharable),即在内存中只有一份代码区,如编译器,假如同时有多个编译任务在执行,这些编译任务会共享编译器的代码区,但同时各个编译任务又有自己独立的区域。

2. .rodata

只读数据区(read-only section)。包含:只读全局变量,只读字符串变量,只读静态(static)变量。程序执行时由加载器(loader)从可执行文件拷贝到内存中。

3. .data

可写数据区(RW section)。包括:可写全局变量,可写静态(static)变量。程序执行时由加载器(loader)从可执行文件拷贝到内存中。

4. . bss

未初始化数据区(un-initialized section)。包括:未初始化或初始化为零的全局变量,未初始化或初始化为零的静态(static)变量。为了减小可执行文件的大小,在可执行文件中bss区只是一个占位符。在程序执行时,加载器(loader)根据bss区的大小,在内存中开辟相应空间,同时将这些内存空间全部初始化为零。

.text, .rodata, .data, .bss四个区域,统称为编译时内存(compiler-time memory),顾名思义,这些区域的大小在编译时就可以决定。

5. heap

堆区。对于C语言而言,heap指程序运行时(run-time)由malloc, calloc, realloc等函数分配的内存。

6. stack

栈区。每一次函数调用,都会发生一次压栈操作,被压栈数据称为一个栈帧(stack frame),有多少次函数调用(包括main()函数),栈区就有多少个栈帧。相应的,每一次函数调用返回,都会相应的发生一次出栈操作,栈帧就会减少一个。

函数调用时,根据压栈的顺序,依次需要压栈的数据包括:调用函数(caller funtion)的上下文环境(context environment),如寄存器;函数返回地址;被调用函数(called funtion)的参数列表;被调用函数的非静态(static)局部变量。

当栈区溢出(stack overflow/underflow)时,栈区数据被污染,程序执行错误,甚至“跑飞"(函数返回地址被修改)。

7. 示例代码

1 /* empty-main.c */
2 #include <stdio.h>
3
4 int main(void)
5 {
6     return 0;
7 }
 1 /* hello-mac.c */
 2
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5
 6 int g_init_2[2] = {1, 2};            /* .data */
 7 const int gc_int_3[3] = {1, 2, 3};   /* .rodata */
 8 int g_initWithZero_4[4] = {0};       /* .bss */
 9 int g_unInit_5[5];                   /* .bss */
10
11 extern int mac(int a, int b, int c);
12
13 int main(void)
14 {
15     static int s_init_6[6] = {1, 2, 3, 4, 5, 6};            /* .data */
16     static const int sc_int_7[7] = {1, 2, 3, 4, 5 ,6, 7};   /* .rodata */
17     static int s_initWithZero_8[8] = {0};                   /* .bss */
18     static int s_unInit_9[9];                               /* .bss */
19
20     int mac_out;                            /* stack */
21     int *heap_10 = (int*)malloc(10 * sizeof(int)); /* heap */
22
23     mac_out = mac(1, 2, 3);
24     printf("mac=%d\n", mac_out);    /* .rodata string */
25
26     free(heap_10);
27     return 0;
28 }
1 /* mac.c */
2
3 #include <stdio.h>
4
5 int mac(int a, int b, int c)
6 {
7     return a + b * c;??
8 }

二. 如何获得compiler-time memory consumption:.text, .rodata, .data, .bss                                                 

首先,必须说明的是,下面提到的三种方法,题主也有很多没有弄明白的地方,尤其是对于对齐的考虑。不过使用objdump -x的方法,可以很清楚的验证,上面示例代码中对变量属于哪个区的描述都是正确的。

1. 借助size/objdump等工具

首先,我们使用gcc在Linux平台编译链接上面的hello-mac.c和mac.c两个源文件:

$ gcc -o hello-mac hello-mac.c mac.c
$ gcc -o empty-main empty-main.c

然后我们可以使用size或者objdump工具来比较两个可执行文件的区别。

我们尝试用size命令看看:

$ size hello-mac empty-main
   text    data     bss     dec     hex filename
   1540     616     184    2340     924 hello-mac
   1115     552       8    1675     68b empty-main

其中text表示只读区(.text和.rodata),data为.data初始化的全局变量或静态变量,bss表示未初始化全局变量或静态变量。dec为前三者的和,hex为dec列的16进制表示。

这里之所以使用empty-main,是为了剔除掉glibc等系统占用的内存。

比较两个可行文件的data区,我们发现hello-mac多出64 byte, 而实际上我们的代码中一共有.data:4 * (2(g_init_2)+ 6(s_init_6)) =  32 byte, why??? ==> align???

再比较bss区,hello-mac多出172 byte,而实际上我们的代码中一共有.bss: 4*(4 + 5 + 8 + 9)= 104 bytes, why???

我们尝试用objdump -x命令:

$ objdump -x hello-mac
$ objdump -x empty-main

在输出中,我们能查看到更加详细的信息,比size的信息要多得多。包括我们前面定义的全局变量和静态变量分别属于.rodata, .data和.bss,均有清晰的交待。

比较两个输出文件的.data,我们发现hello-mac多出了0x28=40byte,与size命令给出的64byte不符,与实际32 byte并不相符。why???

比较bss区,hello-mac多出0xb0=176byte,与size命令给出的172byte不符,与实际算出的104byte也不相符。why???

比较rodata区,hello-mac多出72byte,与实际算出的40byte不符,why??

2. 借助于链接器选项,生成map文件

对于GCC,添加-Xlinker -Map=<filename>到链接器选项即可;对于ARMCC,添加-L--map -L--list=<filename>到链接器选项即可;对于MSVS,按照Linker->debugging->Generate Map Files -> Yes修改就可以得到可执行文件的map问价。我们再分析map文件就可以了。下面用gcc做实验。

$ gcc -c hello-mac.c -o hello-mac.o
$ gcc -c mac.c -o mac.o
$ gcc -o hello-mac -Xlinker -Map=hello-mac.map hello-mac.o mac.o

通过查看map文件我们能清晰的看出来哪个源文件(在map中为上面生成的.o目标文件)包含哪些函数,变量,各自占了.text, .rodata, .data, .bss多少空间。这个方法对于全局变量,函数代码区大小都能查到,但是,静态变量并没有被查到。

三. 如何获得run-time memory consumption: heap, stack                                                                         

在上面的内存模型中,我们会发现heap和stack是向着相反的方向增长,那么,如果两者相遇重叠了会发生什么?要么发生heap的数据被stack覆盖,或者相反。

在调试程序时,常常会遇到“Segment Fault”, “Stackoverflow", "Heap crash”, 最常见的原因就是在于此。那么

I.  是否能在程序运行时获取程序当前的stack, heap大小,以及stack, heap的总容量呢?

II. 有时一个平台上出现SegFault,但是在另一个平台就没有了,如数组越界访问,为什么?

这部分还不知道有什么工具能看。TBD

四. 参考                                                                                                                                               

http://blog.sina.com.cn/s/blog_af9acfc60101bbcy.html

http://blog.csdn.net/gl23838/article/details/7924254

http://www.geeksforgeeks.org/memory-layout-of-c-program/

C语言内存模型 (C memory layout)

时间: 2024-08-04 21:24:43

C语言内存模型 (C memory layout)的相关文章

c语言内存模型

文章一.C语言的内存分配模型 1.程序代码区:存放函数体的二进制代码. 2.全局区数据区:全局数据区划分为三个区域.全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.常量数据存放在另一个区域里.这些数据在程序结束后由系统释放.我们所说的BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Block Started by Symbol的简称. 3.栈区:由编译器自动

java内存模型详解

内存模型 (memory model) 内存模型描述的是程序中各变量(实例域.静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节. 不同平台间的处理器架构将直接影响内存模型的结构. 在C或C++中, 可以利用不同操作平台下的内存模型来编写并发程序. 但是, 这带给开发人员的是, 更高的学习成本.相比之下, java利用了自身虚拟机的优势, 使内存模型不束缚于具体的处理器架构, 真正实现了跨平台.(针对hotspot jvm, jrockit等不同的

深入理解Java内存模型(七)——总结

处理器内存模型 顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照.JMM和处理器内存模型在设计时会对 顺序一致性模型做一些放松,因为如果完全按照顺序一致性模型来实现处理器和JMM,那么很多的处理器和编译器优化都要被禁止,这对执行性能将会有很大的影 响. 根据对不同类型读/写操作组合的执行顺序的放松,可以把常见处理器的内存模型划分为下面几种类型: 放松程序中写-读操作的顺序,由此产生了total store ordering内存模型(简称为TS

Java内存模型(JMM)

1. 概述 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标比较能说明问题,它代表着一秒内服务器平均能响应的请求数,而TPS值与程序的并发能力有着非常密切的关系.在讨论Java内存模型和线程之前,先简单介绍一下硬件的效率与一致性. 2.硬件的效率与一致性 由于计算机的存储设备与处理器的运算能力之间有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理

java内存模型二

并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之间操作发生相对顺序的机制.在共享内存并发

深入理解Java虚拟机- 学习笔记 - Java内存模型与线程

除了在硬件上增加告诉缓存之外,为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果一致,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致,因此,如果存在一个计算任务依赖另外一个计算任务的中间结果,那么其顺序性并不能靠代码的先后顺序来保证.与处理器的乱序优化执行类似,Java虚拟机的即时编译器中也有类似的指令重排序(Instruction

深入理解java内存模型

深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之

深入理解JMM(Java内存模型) --(七)总结

JMM 掌管着一个线程对内存的动作 (读和写)影响其他线程对内存的动作的方式.由于使用处理器寄存器和预处理 cache 来提高内存访问速度带来的性能提升,Java 语言规范(JLS)允许一些内存操作并不对于所有其他线程立即可见.有两种语言机制可用于保证跨线程内存操作的一致性――synchronized 和 volatile.按照 JLS 的说法,"在没有显式同步的情况下,一个实现可以自由地更新主存,更新时所采取的顺序可能是出人意料的."其意思是说,如果没有同步的话,在一个给定线程中某种

【转】深入理解Java内存模型(七)——总结

处理器内存模型 顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照.JMM和处理器内存模型在设计时会对顺序一致性模型做一些放松,因为如果完全按照顺序一致性模型来实现处理器和JMM,那么很多的处理器和编译器优化都要被禁止,这对执行性能将会有很大的影响. 根据对不同类型读/写操作组合的执行顺序的放松,可以把常见处理器的内存模型划分为下面几种类型: 放松程序中写-读操作的顺序,由此产生了total store ordering内存模型(简称为TSO)