博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【C/C++】C语言内存模型 (C memory layout)
阅读量:4625 次
发布时间:2019-06-09

本文共 4751 字,大约阅读时间需要 15 分钟。

 一. 内存模型                                                                                                                                    

 

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 
3 4 int main(void)5 {6 return 0;7 }
1 /* hello-mac.c */ 2  3 #include 
4 #include
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 
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工具来比较两个可执行文件的区别。

1.1 我们尝试用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等系统占用的内存。

1.2 我们尝试用objdump -x命令:

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

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

    在输出的map文件中,我们可以看到main和mac两个函数的.text大小,并求他们的和。

    在计算其他区的时候,由于该方法打印的结果能看到每个函数,每个变量属于什么区,占多少内存,因此可以精确的计算出size大小。

 

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 $ grep "\.text.*mac.o" ./hello-mac.map | awk '{print $3}' | awk '{sum+=$1} END {print "sum=", sum}' # get .text memory

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

 3. 对比

 

  .text .rodata .data .bss
size
(hello-mac SUB empty-main)
425 64 176
objdump -x 113 44 32 104
map file 113 60 40 128
by hand \ 48 32 104

    结论:size方法虽然减掉了empty-main,但明显还是引入了其他的glibc里的东西,导致计算偏大;objdump -x能够得出最详细且准确的信息,但是该方法由于没有给出每个变量和函数属于哪个源文件,因此对于大型软件的统计不利;map file的方法则是前面两种方法的折中,既能快速自动化的算出结果,结果又非常接近真实值。

 三. 如何获得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/

转载于:https://www.cnblogs.com/xjsxjtu/p/3795105.html

你可能感兴趣的文章
asp.net mvc上传文件
查看>>
bitmq集群高可用测试
查看>>
主成分分析(PCA)原理详解
查看>>
短信验证接口网址
查看>>
Geohash距离估算
查看>>
Demon_背包系统(实现装备栏,背包栏,可以切换装备)
查看>>
记录:一次数据库被恶意修改配置文件的问题
查看>>
redis 持久化
查看>>
解决Jupyter notebook[import tensorflow as tf]报错
查看>>
Windows平台下使用ffmpeg和segmenter实现m3u8直播点播
查看>>
python网络画图——networkX
查看>>
ubuntu16.04文件形式安装mongodb
查看>>
SpringBoot------ActiveMQ安装
查看>>
详细了解 int? 类型
查看>>
字符串匹配 ?kmp : hash
查看>>
mongod.service: control process exited, code=exited status=1
查看>>
c# 发送邮件、附件 分类: C# 2014-12-...
查看>>
对360来说,江湖上再无“搜狗”这个传说
查看>>
composer
查看>>
OpenCV特征点检测——ORB特征
查看>>