C程序的内存管理

熟悉Java语言的肯定知道,Java中内存管理是由虚拟机帮助我们完毕的,在C/C++中可不是这样,程序猿须要自己去分配和回收内存空间。本文记录了C程序可运行文件的存储结构、在内存中的存储结构等方面的内容。下面C程序所使用的编译器版本号是GCC 4.4.7。

从一个C程序说起

文件的结构

对于下面这段Hello.c程序再熟悉只是了

#include<stdio.h>

int main(void)
{
printf("Hello World\n");
return 0;
}

以下使用gcc编译它,然后运行可运行文件,再查看可运行文件的存储结构

能够看出,可运行文件Hello在存储时(没有调入内存时)分为代码区(text),数据区(data)和未初始化数据区(bss)3个部分。另外3个字段中,dec表示十进制总和。hex表示十六进制总和。filename表示文件名称。各段的详细说明例如以下:

(1)代码段(text segment):存放CPU运行的机器指令。通常代码区是能够共享的(即另外的运行程序能够调用它)。代码区一般是仅仅读的,以防止程序意外的改动它的指令。常量数据在编译时在代码区分配内存。代码区的指令包括操作码和操作对象(或对象的地址引用)。假设是马上数,就直接包括在代码中;假设是局部数据,将在运行时的栈空间中分配,然后在引用该数据的地址;假设是bss区和数据区。在代码中相同是引用该数据的地址。

(2)全局初始化数据区/静态数据区(initialized data segment/data segment),或者简称数据段:该区域包括了在程序中明白被初始化的全局变量。已经初始化的静态变量(包括全局静态变量和局部静态变量)。

须要注意的是。被const声明的变量和字符串常量在代码段中分配内存。这和汇编语言中的数据段的概念是类似的。

(3)未初始化数据区bss(Block Started By Symbol):存储的是未初始化的全局变量和未初始化的静态变量。

bss区域的数据在程序运行前会被内核初始化为0或者空指针(NULL),这和栈中的变量是不同的,栈中的变量(局部变量)假设没有初始化就使用,系统会随机分配一个值给它,这是不安全的。

上述这些都是可执行文件的存储结构分析,事实上执行时的内存结构和这个十分类似,仅仅只是多了堆内存和栈内存区域,在后面会分析到。以下通过几个样例验证之。

还是以Hello.c程序为例

我们在Hello.c中添加了一句代码,定义一个常量i,通过分析比較。能够发现代码段text区大小添加了4个字节(一个int类型占4个字节),其它区域不变,可知常量是分配在代码段的。

在上述的基础上。在加入一句,定义一个全局变量a。并给它赋值为2。观察各区域变化

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGlhb3JlbnhpYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" style="font-size:14px">

通过比較发现,仅仅有数据段的大小添加了4个字节,也证明了明白被初始化的全局变量是被分配在数据区的。静态变量也是一样。可自行证之。

在上述的基础上。我们在定义一个全局变量b,可是这一个不要赋值。观察各区域变化

能够发现。这一次仅仅有bss区域添加4个字节。也证明了未初始化的全局变量是分配在bss区域的。未初始化的静态变量同理,可自行证之。

进程的结构

一个程序运行的时候就表现为一个或者多个进程,事实上进程内核的数据结构和上述文件的存储结构非常相似,主要是多了堆内存和栈内存区域。基本的布局例如以下图所看到的

各部分说明例如以下:

(1)代码区(text segment):载入的是上述可运行文件的代码段,其载入到内存中的位置由载入器完毕。

(2)全局初始化数据区/静态数据区(Data Segment):载入的是上述可执行文件的数据段。位置位于可执行代码段后面,能够是不相连的。在程序执行之初就为数据段申请了空间,程序退出的时候释放空间,其生命周期是整个程序的执行时期。

(3)未初始化数据区(BSS):载入的是上述可运行文件的BSS段,位置在数据段之后,能够不相连。

其生命周期和数据段一样。

(4)栈区(Stack):由编译器自己主动分配释放,存放函数的參数值、返回值、局部变量等。在程序执行过程中动态的分配和释放。栈区位于BSS后,是向上有限扩展的。

(5)堆区(Heap):用于动态内存分配。位于栈区的后面。是向下有限扩展的。一般由程序猿进行分配和释放,若不释放。在程序结束的时候,由OS负责回收。

堆与栈的差别

栈是由编译器在程序执行时分配的内存空间。由操作系统维护(这和Java虚拟机中的栈内存是类似的)。堆是由malloc( )函数(C++中的new)分配内存。内存的管理由程序猿手动控制。在C语言中使用free( )函数完毕释放(C++中是delete)。

堆和栈的主要差别有下面几点:

(1)管理方式不同。程序在执行时,栈由操作系统自己主动管理,堆由程序猿手动管理,堆内存的管理更easy造成内存的泄漏。

(2)空间大小不同。

栈是向低地址扩展的(參考上图)是一块连续的内存空间。栈的容量是预先设定好的,假设申请的栈空间大于该预设值,将会出现栈溢出错误。堆是向高地址扩展的,是不连续的内存空间。系统是採用链表管理空暇的内存地址的,且链表的遍历方向是由低地址向高地址的。

(3)产生的碎片不同。在堆中频繁的使用malloc/free(new/delete)势必会再次内存空间的不连续,产生大量的内存碎片,使程序执行效率减少。而在栈内存中,则全然不会存在这种问题。

(4)扩展方向不同。在x86平台上,堆是向上扩展的。即向内存地址添加的方向。栈是向下扩展的,即向内存地址减小的方向。

和前面一样。以下使用一个样例去验证执行时的存储分布

#include <stdio.h>
#include <malloc.h>
#include <unistd.h>
#include <alloca.h> extern void afunc(void);
extern etext,edata,end;<span style="white-space:pre"> </span> int bss_var; //no init globel data must be in bss int data_var=42; //init globel data must be in data #define SHW_ADR(ID,I) printf("the %8s\t is at adr:%8x\n",ID,&I); //the macro to printf the addr int main(int argc,char *argv[])
{
char *p,*b,*nb; printf("Adr etext:%8x\t Adr edata %8x\t Adr end %8x\t\n",&etext,&edata,&end); printf("\ntext Location:\n");
SHW_ADR("main",main); //text section function
SHW_ADR("afunc",afunc); //text section function printf("\nbss Location:\n");
SHW_ADR("bss_var",bss_var); //bss section var printf("\ndata location:\n");
SHW_ADR("data_var",data_var); //data section var printf("\nStack Locations:\n");
afunc(); p=(char *)alloca(32); //alloc memory from statck
if(p!=NULL)
{
SHW_ADR("start",p);
SHW_ADR("end",p+31);
} b=(char *)malloc(32*sizeof(char)); //malloc memory from heap
nb=(char *)malloc(16*sizeof(char)); printf("\nHeap Locations:\n");
printf("the Heap start: %p\n",b);
printf("the Heap end:%p\n",(nb+16*sizeof(char)));
printf("\nb and nb in Stack\n");
SHW_ADR("b",b);
SHW_ADR("nb",nb);
free(b);
free(nb);
} void afunc(void)
{
static int long level=0; //data section static var
int stack_var; //temp var ,in stack section
if(++level==5)
{
return;
}
SHW_ADR("stack_var in stack section",stack_var);
SHW_ADR("Level in data section",level);
afunc();
}

当中须要说明的,etext、edata和end(能够理解为end of text、end of data和end of bss)是3个外部的全局变量,是跟用户进程有关的虚拟地址。分别标志着代码段的结束、数据段的结束和bss段的结束。

使用gcc编译上述程序,并执行。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGlhb3JlbnhpYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

通过观察执行时各区域的内存地址,能够得到以下的内存分布图(每个执行环境,以下的内存地址值会不一样,但相应关系不变)

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGlhb3JlbnhpYW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

须要说明的是,各区域之间并非相连的。各区域在图中的大小,并不代表实际的大小。从上面还能够发现,afunc函数中的静态变量level四次打印的地址是一样的,而局部变量stack_var四次打印的地址各不同样,也就是说静态变量在整个程序的生命周期内仅仅会被载入初始化一次,并且分配在数据段;而局部变量被分配到栈区,其生命周期是当前函数,每一次进入函数都会又一次载入初始stack_var。

C程序内存管理的更多相关文章

  1. C++复习12.程序内存管理

    程序内存管理 20131006 一个程序在运行期间的内存是如何的对编写程序至关重要,之前整理的C++内存管理的知识和Java程序内存管理的知识.今天我们系统的整理一下程序的内存. 1.一个程序的内存有 ...

  2. Java复习2.程序内存管理

    前言: 国庆节的第三天,大家都回家了,一个人在宿舍好无聊.不过这年头与其说是出去玩不如是说出去挤,所以在学校里还是清闲的好.找工作不用担心了,到时候看着你们慢慢忙:插个话题,大学都没有恋爱过,总之各种 ...

  3. 笔记:程序内存管理 .bss .data .rodata .text stack heap

    1.未初始化的全局变量(.bss段) bss段用来存放 没有被初始化 和 已经被初始化为0 的全局变量.如下例代码: #include<stdio.h> int bss_array[102 ...

  4. Android 内存管理(二)

    很多开发者都是从j2me或j2ee上过来的,对于内存的使用和理解并不是很到位,Android开发网本次给大家一些架构上的指导,防止出现豆腐渣工 程的出现.Android作为以Java语言为主的智能平台 ...

  5. iOS内存管理策略和实践

    转:http://www.cocoachina.com/applenews/devnews/2013/1126/7418.html 内存管理策略(memory Management Policy) N ...

  6. 黑马程序员_ Objective-c 内存管理笔记

    引用计数器 当一个对象被创建出来,就要分配给内存这个对象,当不用这个对象的时候,就要及时的回收,为了可以明确知道对象有没有被使用,就要用引用计数器来体现,只要计数器不为0,表明对象被使用中. 1.方法 ...

  7. [转载]Java应用程序中的内存泄漏及内存管理

    近期发现测试的项目中有JAVA内存泄露的现象.虽然JAVA有垃圾回收的机制,但是如果不及时释放引用就会发生内存泄露现象.在实际工作中我们使用Jprofiler调用java自带的 jmap来做检测还是很 ...

  8. 程序员必读:Linux内存管理剖析

    现在的服务器大部分都是运行在Linux上面的,所以作为一个程序员有必要简单地了解一下系统是如何运行的. 对于内存部分需要知道: 地址映射 内存管理的方式 缺页异常 先来看一些基本的知识,在进程看来,内 ...

  9. 黑马程序员——OC的内存管理学习小结

    内存管理在Objective-C中的重要性就像指针在C语言中的重要程序一样. 虽然作为一门高级语言,但OC却没有内存回收机制.这就需要开发者来对动态内存进行管理.OC中内存管理的范围是:任何继承了NS ...

随机推荐

  1. php 扩展编译linux

    进入扩展库目录:$cd phpredis-master 需要root权限执行 执行:$ phpize 执行:$ ./configure 执行:$ make 执行:$ make install   编译 ...

  2. 对c++服务端进行覆盖率统计

    (1)首先需要为每个被测程序的所有编译文件增加选项,如果文件太多,这无疑是灾难,可利用spec文件达到目的 sed -i '$ a\export LD_PRELOAD=/usr/local/bin/c ...

  3. WebApi(二)-重新封装返回结果

    先创建要返回的结果类型: /// <summary> /// 返回类型 /// </summary> public class ApiResultModel { private ...

  4. JSON取代XML?--JSON入门指南

    定义 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C# ...

  5. jx3dps开发日记

    2014.11.13 子级过滤 关于optgroup这个东西,一开始以为是个包裹元素,但是一般来说,包裹元素给一个class,那么让这个class show()应该它包裹的元素也跟随show,可结果是 ...

  6. MockupBuilder

    玩一下,想起了以前公司产品经理作的些事了...

  7. Condition 的使用

    Condition 将 Object 监视器方法(wait.notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set ...

  8. 11.在Global的Application_Error处理错误示例

    Application_Error是在程序出问题时触发的事件. 这里面要用到错误页的情况,所以要配置web.config的customError项. 1.建立Global文件,在它的Applicati ...

  9. LeetCode解题报告:Binary Tree Postorder Traversal

    Given a binary tree, return the postorder traversal of its nodes' values. For example:Given binary t ...

  10. sort merge join,hash join,netsloop join

    Join Operations ? SORT-MERGE JOIN – Sorts tables on the join key and then merges them together – Sor ...