我的研发面经_程序内存结构分析①C语言

可执行文件的数据存储+c语言内存分区

参考:https://blog.csdn.net/love_gaohz/article/details/41310597
https://www.cnblogs.com/southcyy/p/10167005.html

一、ELF文件数据格式分析:

在学习之前我们先看看ELF文件的存储格式。

在这里插入图片描述
ELF分为三种类型
a. x.o 可重定位文件(relocalble file)
b.可执行文件
c.共享库(shared library)
三种格式基本上从结构上是一样的,只是具体到每一个结构不同。下面我们就从整体上看看这3种格式从文件内容上存储的方式,spec上有张图是比较经典的:如上图:

其实从文件存储的格式来说,上面的两种view实际上是一样的,Segment实际上就是由section组成的,将相应的一些section映射到一起就叫segment了,就是说segment是由0个或多个section组成的,实际上本质都是section。

在这里我们首先来仔细了解一下section和segment的概念:section就是相同或者相似信息的集合,比如我们比较熟悉的x.text、x.data、x.bss 的section,x.text是可执行指令的集合,x.data是初始化后数据的集合,x.bss是未初始化数据的集合。实际上我们也可以将一个程序的所有内容都放在一起,就像dos一样,但是将可执行程序分成多个section是很有好处的,比如说我们可以将.text section放在memory的只读空间内,将可变的.data section放在memory的可写空间内。

从可执行文件的角度来讲,如果一个数据未被初始化那就不需要为其分配空间,所以.data和.bss一个重要的区别就是.bss并不占用可执行文件的大小,它只是记载需要多少空间来存储这些未初始化数据,而不分配实际的空间。

二、存储空间分段

站在高级语言的角度,一个程序分为如下段:

 1. text
 2. data (initialized)
 3. bss
 4. stack 
 5. heap

1.一个程序的3个基本段:text段,date段,bss段。

一般情况下,一个可执行二进制程序(更确切的说,一个进程单元)在存储(没有调入到内存运行)时拥有3个部分,分别是代码段(text)、数据段(data)和BSS段。text段在内存中被映射为只读,但date段与bss段是可写的。

a.text段:代码段,就是放程序代码的,编译时确定,只读。

b.date段:存放在编译阶段(而非运行时)就能确定的数据,可读可写。也就是通常所说的静态存储区,赋了初值的全局变量赋初值的静态变量存放在这个区域,常量也存在这个区域。

c.bss段:已经定义但没赋初值的全局变量和静态变量存放在这个区域。

可执行二进制程序 = 代码段(text)+数据段(data)+BSS段

2.而当程序被加载到内存单元时,则需要另外两个域:堆域(stack)和栈域(heap)。

图所示为可执行代码存储态和运行态的结构对照图。一个正在运行的C程序占用的内存区域分为代码段(text)、初始化数据段(data)、未初始化数据段(BSS)、堆、栈5个部分。

正在运行的C程序 = 代码段+初始化数据段(data)+未初始化数据段(BSS)+堆+栈

3.加载各部分内存的主体

在将应用程序加载到内存空间执行时,操作系统负责代码段、数据段和BSS段的加载,并将在内存中为这些段分配空间。栈亦由操作系统分配和管理,而不需要程序员显示地管理;堆段由程序员自己管理,即显示地申请和释放空间。

4.动态分配与静态分配,二者最大的区别在于:

  1. 直到Run-Time的时候,执行动态分配,而在compile-time的时候,就已经决定好了分配多少Text+Data+BSS+Stack。
  2. 通过malloc()动态分配的内存,需要程序员手工调用free()释放内存,否则容易导致内存泄露,而静态分配的内存则在进程执行结束后系统释放(Text, Data), 但Stack段中的数据很短暂,函数退出立即被销毁。

在这里插入图片描述
图1-1(从可执行文件a.out的角度来讲,如果一个数据未被初始化那就不需要为其分配空间,所以.data和.bss一个重要的区别就是.bss并不占用可执行文件的大小,它只是记载需要多少空间来存储这些未初始化数据,而不分配实际的空间)

三、各分段内存的解析

代码段 --text(code segment/text segment)
text段在内存中被映射为只读,但.data和.bss是可写的。
text段是程序代码段,在AT91库中是表示程序段的大小,它是由编译器在编译连接时自动计算的,当你在链接定位文件中将该符号放置在代码段后,那么该符号表示的值就是代码段大小,编译连接时,该符号所代表的值会自动代入到源程序中。

数据段 – data
data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小在编译连接时自动分配,它和你的程序大小没有关系,但和程序使用到的全局变量,常量数量相关。数据段属于静态内存分配。

bss段–bss
bss是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。BSS段属于静态内存分配。它的初始值也是由用户自己定义的连接定位文件所确定,用户应该将它定义在可读写的RAM区内,源程序中使用malloc分配的内存就是这一块,它不是根据data大小确定,主要由程序中同时分配内存最大值所确定,不过如果超出了范围,也就是分配失败,可以等空间释放之后再分配。BSS段属于静态内存分配。

stack:
栈(stack)保存函数的局部变量(但不包括static声明的变量, static 意味着 在数据段中 存放变量),参数以及返回值。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。

heap:
堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。

小结:
在这里插入图片描述
一个典型C内存空间分布图:
在这里插入图片描述

四、存储类型关键字定义变量与函数作用域与生命周期

什么玩意

auto变量:函数的局部变量,如果没有声明为static,函数中定义的局部变量全部为auto类型,auto变量包括未加static声明的局部变量和函数的形参。在函数调用时系统会给他们分配存储空间,在函数调用结束后会自动释放这些空间。属于动态存储方式。

static变量:用static声明的局部变量在调用结束后不会消失而保存原来的值。static局部变量定义使用后值会存储下来。所以使用static局部变量定义只需要一次赋值。静态局部变量的作用域仅限于所定义的函数。但函数结束后变量的值会保留。直到整个程序运行结束。全局变量从定义开始作用于整个文件直至程序运行结束。

register寄存器变量:寄存器变量可以提高c语言的执行效率,即将局部变量的值存入CPU的寄存器中。需要注意的是!!!:1.只有动态存储的变量(自动局部变量和形参)才可以作为寄存器变量来存储,局部静态变量不可以定义为寄存器变量。2.计算机的寄存器数目是有限的,所以不能定义任意多个寄存器变量。

extern外部变量:即全局变量的外部表现形式,是在函数外部定义的变量。全局变量的作用域为从定义开始到源文件结束。exten对该变量作外部变量声明,扩展变量作用域。

五、堆区与栈区的差异

1.申请方式
stack:栈区;由系统自动分配,自动开辟空间。
heap:由程序员自己申请并指明大小,c中malloc,c++中new。如p1=(char*)malloc(10);p2=(char*)new(10);但需要注意的是p1,p2本身是在栈中的

2.申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
堆:首先操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。另外对于大部分系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆节点大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

3.申请大小的限制
栈:在windows下栈是向低地址扩展的数据结构,是一块连续的内存区域。所以栈的栈顶地址和最大容量是系统预先设定好的。在windows下栈的大小是2M.因此能从栈获得的空间比较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是是由于系统用链表来存储空闲内存地址的,所以是不连续的。而链表的遍历方向是由低地址到高地址。堆得大小受限于计算机系统中有效的虚拟内存大小。相比较而言堆获得的空间比较灵活,也比较大。

4.申请效率的比较
栈:由系统自动分配,速度较快,但程序员是无法控制的。
堆:由new分配的内存,一般速度比较慢,而且比较容易产生内存碎片,不过用起来最方便。

5.堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数。在大多数c编译器中,参数是由右往左压栈的,然后是函数中的局部变量。静态变量是不入栈的。当函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,,也就是主函数的下一条指令,程序由该点继续执行。
堆:一般是在堆的头部用一个字节存放堆得大小,其他内容自己安排。

6.存取效率的比较
1 char str1[]=“aaaaaa”;
2 char *str2=“cccccc”;
第一行是在运行时刻赋值的,第二行是在编译时就已经确定的,但在以后的存取过程中,在栈上的数组比指针指向的字符串快。

六、可执行文件大小由什么决定?

可执行文件在存储时分为代码段、数据段和BSS段三个部分。

【例一】
程序1:
int ar[30000];
void main()
{

}
程序2:
int ar[300000] = {1, 2, 3, 4, 5, 6 };
void main()
{

}
发现程序2编译之后所得的.exe文件比程序1的要大得多。当下甚为不解,于是手工编译了一下,并使用了/FAs编译选项来查看了一下其各自的.asm,发现在程序1.asm中ar的定义如下:
_BSS SEGMENT
?ar@@3PAHA DD 0493e0H DUP (?) ; ar
_BSS ENDS
而在程序2.asm中,ar被定义为:
_DATA SEGMENT
?ar@@3PAHA DD 01H ; ar
DD 02H
DD 03H
ORG $+1199988
_DATA ENDS
区别很明显,一个位于.bss段,而另一个位于.data段,两者的区别在于:全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;而函数内的自动变量都在栈上分配空间。

.bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化,因此造成了上述情况。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值