c语言内存管理和使用(一文搞懂C语言内存模型与栈)

一,内存模型

在C语言中,内存可分用五个部分:

  • 1. BSS段(Block Started by Symbol): 用来存放程序中未初始化的全局变量的内存区域。
  • 2. 数据段(data segment): 用来存放程序中已初始化的全局变量的内存区域。
  • 3. 代码段(text segment): 用来存放程序执行代码的内存区域。
  • 4. 堆(heap):用来存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc分配内存时,新分配的内存就被动态添加到堆上,当进程调用free释放内存时,会从堆中剔除。
  • 5. 栈(stack):存放程序中的局部变量(但不包括static声明的变量,static变量放在数据段中)。同时,在函数被调用时,栈用来传递参数和返回值。由于栈先进先出特点。所以栈特别方便用来保存/恢复调用现场。

c语言内存管理和使用(一文搞懂C语言内存模型与栈)(1)

嵌入式进阶教程分门别类整理好了,看的时候十分方便,由于内容较多,这里就截取一部分图吧。

c语言内存管理和使用(一文搞懂C语言内存模型与栈)(2)

需要的朋友私信【内核】即可领取

如下往上,分别是text段,data段,BSS段,堆,栈

c语言内存管理和使用(一文搞懂C语言内存模型与栈)(3)

由上图可知:

0x0000 0000:保留区域, 最底层

  • 代码区:用来存放程序代码和常量,只读(运行期会一直存在)
  • 常量区:一般常量,字符常量,只读(运行期会一直存在)
  • 全局数据区:全局变量和静态变量,可读写(运行期会一直存在)
  • 堆段:malloc/free的内存,malloc时分配,free时释放(向上增长)

未分配堆内存

0x4000 0000:动态链接库

未分配栈内存

栈段:局部变量,函数调用参数返回值(向上增长)

0xc000 0000 ~ 0xffff ffff:内核空间(1G)

二,栈详解

栈(stack): 是由系统自动分配和释放,存放函数的参数值,返回值,局部变量等。其操作方式类似于数据结构中的栈。

2.1栈的申请

1. 当在函数或块内部声明一个局部变量时,如:int nTmp; 系统会判断申请的空间是否足够,足够,在栈中开辟空间,提供内存;不够空间,报异常提示栈溢出。

2. 当调用一个函数时,系统会自动为参数当局部变量,压进栈中,当函数调用结束时,会自动提升堆栈。(可查看汇编中的函数调用机制)

2.2栈的大小

栈是有一定大小的,通常情况下,栈只有2M,不同系统栈的大小可能不同。

在linux中,查看进程/线程栈大小,命令:

ulimit -s

$ ulimit -s

$ 8192

我的系统中栈大小为 8192, 有些系统为 10240, 具体查看自已系统栈大小

设置栈大小:

  • 1. 临时改变栈大小:ulimit -s 10240
  • 2. 开机设置栈大小:在/etc/rc.local中加入 ulimit -s 10240
  • 3. 改变栈大小: 在/etc/security/limits.conf中加入* soft stack 10240

所以,在声明局部变量时,新手要特别注意栈的大小:

1. 对于局部变量,尽量不定义大的变量,如大数组(大于2*1024*1024字节)char buf[2*1024*1024]; // 可能会导致栈溢出

2. 对于内存较大或不知大小的变量,用堆分配,局部变量用指针,注意要释放char* pBuf = (char*)malloc(2*1024*1024); // char* 为局部变量 malloc的内存在堆free(pBuf);

3. 或定义在全局区中,static变量 或常量区中static char buf[2*1024*1024];

2.3栈的生长方向

栈的生长方向和存放数据的方向相反,自顶向下

2.4 栈分配例子

int function( int var1 ,int var2) {undefined int var3; int var4; }

var1,var2,var3在栈中的图如下:

0xc000 0000

var1

0xc000 0000 - 4

var2

0xc000 0000 - 8

var3

0xc000 0000 - 12

var4

三,堆详解

堆(heap):是用来存放动态申请或释放的区域。需要程序员分配和释放,系统不会自动管理,如果用完不释放,将会造成内存泄露,直到进程结速后,系统自动回收。

3.1 堆的目的

为什么在堆呢?原因很简单,在栈中,大小是有限制的,能常大小为2M,如果需要更大的空间,那么就要用到堆了,堆的目的就是为了分配使用更大的空间。

3.2申请和释放

int function() {undefined char *pTmp = (char*) malloc(1024); // malloc在堆中分配1024字节空间 //pTmp 为局部变量,只占四字节 free(pTmp); // free为手动释放堆中空间 pTmp = NULL; // 防止pTmp变野指针误用 }

3.3堆的大小

堆是可以申请大块内存的区域,但堆的大小到底有多大,下面分析下,以32位系统为例。

在linux中,堆区的内存申请,在32位系统中,理论上:2^32=4G,但如上面的内存分布图可知:内核占用1G空间。

0xFFFF FFFF

1G内核空间

0xC000 0000

0XBFFF FFF

3G用户空间(包text段,data段,BSS段,堆,栈)

0x0000 0000

如上所知,理论上,使用malloc最大能够申请空间大约3G。但这是理论值,因为实际中,还会包含代码区,全局变量区和栈区。

char *buf = (char*) malloc(3GB); // 理论上

3.4 堆的生长方向

如上面的图可知,堆是由低地址向高地址生长的

3.5 堆的注意事项

堆虽然可以分配较大的空间,但有一些要注意的地方,否则会出现问题。

1. 释放问题:分配了堆内存,一定要记得手动释放,否则将会导致内存泄露

void* alloc(int size) {undefined char* ptr = (char*)malloc(size); return ptr; }

上面函数如果外部调用,没有释放,将内存不会释放造成泄露。

2. 碎片问题:如果频繁地调用内存分配和释放,将会使堆内存造成很多内存碎片,从而造成空间浪费和效率低下。

a) 对于比较固定,或可预测大小的,可以程序启动时,即分配好空间,如:某个对象不会超过500个,那个可先生成,object *ptr = (object*)malloc(object_size*500);

b) 结构对齐,尽量使结构不浪费内存

3. 超堆大小问题:如果申请内存超过堆大小,会出现虚拟内存不足等问题

a) 尽量不要申请很大的内存,如直需要,可采用内存数据库等

4. 分配是否成功问题:申请内存后,都在判断内存是否分配成功,分配成功后才能使用,否则会出现段错误

char * pTmp = (char*)malloc(102400); if(pTmp == 0) // 一定在记得判断 {undefined return false; }

5. 释放后野指针问题:释放指针后,一定要记得把指针的值设置成NULL,防止指针被释放后误用

free(pTmp); pTmp = NULL; // 防止变野指针

6. 多次释放问题:如果第5并没置NULL,多次释放将会出现问题。

四,例子

nt g_var = 0; // data段 int g_var1; // BSS 段 char g_str; // BSS 段 char g_str = “hello world”; // g_str data段 , hello world 字段常量区 char* g_ptr = NULL; // data段 int test() {undefined char l_var[1024]; // 栈 g_ptr = (char*)malloc(1024); // mall内存 椎中 static int g_int =1; // data 段中 }

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页