关于c:程序人生-C-语言编译器对内存空间的分配原则

2次阅读

共计 2514 个字符,预计需要花费 7 分钟才能阅读完成。

本文首发于 2015-05-04 14:50:16

概述

一个由 C/C++ 编译的程序占用的内存分为以下几个局部:

  1. 栈区(stack):由编译器主动调配、开释,寄存函数的参数值、局部变量的值等,其操作形式相似于数据结构中的栈。个别大家常说的堆栈和栈是一样的,就是栈(stack),而说 堆 时才是堆 heap。
  2. 堆区(heap):个别由程序员调配开释,若程序员不开释,程序完结时由 OS 回收。留神 它与数据结构中的堆是两回事,调配形式倒是相似于链表
  3. 全局区(动态区,static):全局变量和动态变量的存储是放在一块的,初始化的全局变量和动态变量在一块区域,未初始化的全局变量和未初始化的动态变量在相邻的另一块区域。程序完结后由零碎开释。
  4. 文字常量区:常量字符串就是放在这里的。程序完结后由零碎开释。
  5. 程序代码区:寄存函数体的二进制代码。

举例来说:

//main.cpp 
int a = 0; // 全局初始化区 
char *p1; // 全局未初始化区 
main() 
{ 
int b; // 栈 
char s[] = "abc"; // 栈 
char *p2; // 栈 
char *p3 = "123456"; // 123456\0 在常量区,p3 在栈上。static int c =0;// 全局(动态)初始化区 

// 调配的 10 个和 20 个字节的区域就在堆区。p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 

strcpy(p1, "123456"); // 123456\0 放在常量区,编译器可能会将它与 p3 所指向的 "123456" 优化成同一个地位
} 

在函数体中定义的变量通常是在栈上,用 malloc, calloc, realloc 等分配内存的函数调配失去的就是在堆上。

在所有函数体外定义的是全局量,加了 static 修饰符后不论在哪里都寄存在全局区(动态区),在所有函数体外定义的 static 变量示意在该文件中无效,不能 extern 到别的文件应用,在函数体内定义的 static 示意只在该函数体内无效。

函数中的 ”123456″ 这样的字符串寄存在常量区。

还有就是函数调用时会在栈上有一系列的保留现场及传递参数的操作。

对于堆和栈

1. 内存调配方面

堆: 个别由程序员调配开释,若程序员不开释,程序完结时可能由 OS 回收。它与数据结构中的堆是两回事,调配形式是相似于链表。可能用到的关键字如下:new、malloc、delete、free 等等。

栈: 由编译器 (Compiler) 主动调配开释,寄存函数的参数值,局部变量的值等。其操作形式相似于数据结构中的栈。当一个函数调用完返回后它会开释该函数中所有的栈空间。

2. 申请形式

栈(stack):

由零碎主动调配。例如,申明在函数中一个局部变量 int b;,零碎主动在栈中为 b 开拓空间。

堆(heap):

须要程序员本人申请,并指明大小。

在 C 中 malloc 函数:

p1 = (char *)malloc(10); 

在 C ++ 中用 new 运算符:

p2 = (char *)new char(10); 

但留神 p1、p2 自身是在栈中的。

3. 申请后零碎的响应

栈: 只有栈的残余空间大于所申请空间,零碎将为程序提供内存,否则将报异样提醒 栈溢出

堆: 首先应该晓得操作系统有一个记录闲暇内存地址的链表,当零碎收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,而后将该结点从闲暇结点链表中删除,并将该结点的空间调配给程序。对于大多数零碎,会在这块内存空间中的首地址处记录本次调配的大小 ,这样代码中的 delete 语句能力正确的开释本内存空间。另外,因为找到的堆结点的大小不肯定正好等于申请的大小, 零碎会主动的将多余的那局部从新放入闲暇链表中

4. 申请大小的限度

栈:

  • 在 Windows 下,栈是向低地址扩大的数据结构,是一块间断的内存的区域。这句话的意思是 栈顶的地址 栈的最大容量 是零碎预先规定好的,在 Windows 下,栈的大小个别是 2M,如果申请的空间超过栈的残余空间时,将提醒 overflow。因而,能从栈取得的空间较小。
  • 栈不够用的状况个别是程序中调配了大量数组和递归函数档次太深。
  • 当一个函数调用完返回后它会开释该函数中所有的栈空间。

堆:

  • 堆是向高地址扩大的数据结构,是不间断的内存区域。这是因为零碎是用链表来存储的闲暇内存地址的,天然是不间断的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中无效的虚拟内存。由此可见,堆取得的空间比拟灵便,也比拟大。

5. 申请效率的比拟

由零碎主动调配,速度较快。但程序员是无法控制的。

是由 malloc 调配的内存,个别速度比较慢,而且 容易产生内存碎片,不过用起来最不便。

6. 堆和栈中的存储内容

栈:

  • 在函数调用时,第一个进栈的是主函数后的下一条指令(函数调用语句的下一条可执行语句)的地址,而后是函数的各个参数(在大多数的 C 编译器中,参数是由右往左入栈的),而后是函数中的局部变量。留神动态变量是不入栈的。
  • 当本次函数调用完结后,局部变量先出栈,而后是参数,最初栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点持续运行。

堆:

  • 个别是在堆的头部用一个字节寄存堆的大小。堆中的具体内容有程序员安顿。

7. 存取效率方面

堆: char *s1 = "Hellow Word";是在 编译时 就确定的;

栈:char s1[] = "Hellow Word";是在 运行时 赋值的;用数组比用指针速度要快一些,因为指针在底层汇编中须要用 edx 寄存器 直达一下,而数组在栈上间接读取。

在栈上存取数据比通过指针在堆上存取数据快些。


欢送关注我的微信公众号【数据库内核】:分享支流开源数据库和存储引擎相干技术。

题目 网址
GitHub https://dbkernel.github.io
知乎 https://www.zhihu.com/people/…
思否(SegmentFault) https://segmentfault.com/u/db…
掘金 https://juejin.im/user/5e9d3e…
开源中国(oschina) https://my.oschina.net/dbkernel
博客园(cnblogs) https://www.cnblogs.com/dbkernel
正文完
 0