上次面试直接被面试官说我C语言基础差,主要是内存管理这一块有点不清楚,所以复习一下。
我的介绍会比一般教科书要深入很多,一次性分享的话会有很多内容,怕大家视觉疲劳,所以我打算分成几次分享,希望大家喜欢。
今天主要介绍数据的存储区域,上次考察也主要是这个。
四区分布图
进程空间大致分为这四个部分,但是这个图并不适合我们今天的介绍,因为代码段还可以细分。
目前有这几种区分方法
1、未初始化的全局变量(.bss段)
通俗一点,bss段主要存储没有被初始化和初始化为0的全局变量。下面我们看一段代码。
int bss_array[1024*1024];
int main(int argc,char* argv){
return 0;
}
我们可以在Linux系统中运行如下命令
# gcc -g bss.c -o bss.exe
# ls -l bss.exe
# objdump -h bss.exe | grep bss
运行之后我们会发现,可执行文件的大小只有5KB,而变量bss_array大小为4MB,由此可见,bss类型的全局变量不占用文件空间,只占用运行时的空间。
现代操作系统在加载程序时,会把所有的bss全局变量清0。在嵌入式开发中,为保证程序可移植性,程序员通常会把不想初始化的变量初始化为0。
2、初始化过的全局变量(.data段)
和bss相比,data名字就已经告诉你这段内存存放了数据。如果初始化的数据是0,编译器会优化到bss段去。
我们来看一段代码
int bss_array[1024*1024]={1};
int main(int argc,char* argv){
return 0;
}
在Linux系统执行如下命令
# gcc -g bss.c -o bss.exe
# ls -l bss.exe
# objdump -h bss.exe | grep bss
执行后我们会发现,把初始值改为非0后,可执行文件的大小变成了4MB多。由此可见,data类型的全局变量既占文件空间也占运行空间。同样作为全局变量,整个运行期间data数据也是一直存在的。
3、常量数据段(.rodata段)
ro代表read only,rodata就是用来存放常量数据的。关于rodata数据,需要注意以下几点:
(1)常量不一定存放在rodata中,有的立即数直接和指令编码在一起放在代码段(.text)中。
(2)对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件(EXE/SO)中只存在一份复制。
(3)rodata在多个进程间是共享的,这样可以提高运行效率。
(4)在有些嵌入式系统中,rodata放在ROM(或者NOR Flash)里,运行时直接读取,无需加载到RAM内存中。
(5)在嵌入式Linux系统中,也可以通过一种叫做XIP(就地执行)的技术,也可以直接读取,而无需加载到RAM内存中。
(6)常量是不能修改的,在Linux下修改常量会报段错误。
由此可见,把在运行过程中不会改变的数据设为rodata类型是有好处的,在进程间共享,不占用RAM空间,大大提高了空间利用率。且不能被修改,提高程序的稳定性。我们平常用const修饰的常量就是放在这个区域的,相信有C语言基础的同学都应该知道吧。
4、代码(.text段)
text段主要存放代码(如函数)和部分常量,和rodata段很相似,主要不同在于代码段是可以执行的。
5、栈(stack)
栈用于存放临时变量和函数参数。栈作为一种基本数据结构,可以用来实现函数调用,比如我们做算法时常用到的递归,应该没有比栈更优雅的方式来实现了。
通常情况下,栈向下(低地址)增长,每向栈中PUSH一个元素,栈顶就向低地址扩展,每从栈中POP一个元素,栈顶就往高地址回退。
6、堆(heap)
堆是最灵活的一种内存,他的生命周期完全由使用者控制。标准C语言提供以下几个函数:
(1)malloc:用来分配一块指定大小的内存。
(2)realloc:用来调整/重分配一块存在的内存。
(3)free:用来释放不再使用的内存。
使用堆内存需要注意的问题:
malloc/free要配对使用。内存分配了不释放会造成内存泄漏(Memory Leak),内存泄漏多了会出现Out of memory的错误,再分配内存就会失败。释放时也只能释放已经分配的内存,释放无效内存或者重复free都会造成程序崩溃。分配了多少内存只能使用多少内存,不管是读还是写,都在这个范围内。
面试中有时候会问堆空间和栈空间的区别
(1)栈空间是运行过程中系统自动分配的,其分配和回收都由系统完成,而堆空间需要程序员手动分配,分配和释放都是由程序员决定。
(2)栈空间可使用区域一定是连续的,而堆空间不一定,通过多次的申请释放之后,会有很多空间碎片。
(3)栈空间的生长方式是向下生长,堆空间是向上生长。