C语言内存模型详解

C语言内存模型详解

second60  20180415

1 内存模型

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

1. BSS段(Block Started by Symbol): 用来存放程序中未初始化的全局变量的内存区域。

2. 数据段(data segment): 用来存放程序中已初始化的全局变量的内存区域。

3. 代码段(text segment): 用来存放程序执行代码的内存区域。

4. 堆(heap):用来存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc分配内存时,新分配的内存就被动态添加到堆上,当进程调用free释放内存时,会从堆中剔除。

5. 栈(stack):存放程序中的局部变量(但不包括static声明的变量,static变量放在数据段中)。同时,在函数被调用时,栈用来传递参数和返回值。由于栈先进先出特点。所以栈特别方便用来保存/恢复调用现场。

 

APUE中的一个典型C内存空间分布图

 

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

Linux下32位环境的用户空间内存分布情况

 

 

由上图可知:

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

代码区:用来存放程序代码和常量,只读(运行期会一直存在)

常量区:一般常量,字符常量,只读(运行期会一直存在)

全局数据区:全局变量和静态变量,可读写(运行期会一直存在)

堆段:malloc/free的内存,malloc时分配,free时释放(向上增长)

未分配堆内存

0x4000 0000:动态链接库

未分配栈内存

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

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

 

2栈详解

(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)

{

int  var3;

int  var4;

}


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

0xc000 0000

var1

0xc000 0000 - 4

var2

0xc000 0000 - 8

var3

0xc000 0000 - 12

var4

 

3 堆详解

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

 

3.1 堆的目的

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

3.2申请和释放

int  function()

{

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)

{

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)   // 一定在记得判断

{

return false;

}

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

free(pTmp);

pTmp = NULL; // 防止变野指针

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

 

4 例子

 

int  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()

{

char l_var[1024]; //

g_ptr = (char*)malloc(1024); // mall内存 椎中

static  int  g_int =1; // data 段中

}

 

复习下C语言内存模型,网上虽然有很大内存模型的介绍,但是比较零散,本文详细地介绍了其中的细节,当然也有不完善的地方,欢迎补充。

 

 

 

 

 

 

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页