Linux内存管理的相关知识

Linux内存管理的相关知识  

2010-07-02 14:44:39|  分类: Linux C |  标签:linux内存管理的相关知识   |字号 订阅

内存管理

1、C程序结构

         C程序在没有调入内存之前(也就是在存储时),分为代码区(text)、数据区(data)和未初始化数据区(bss)3个部分。

¨          代码区 存放CPU执行的机器指令,即函数体的二进制代码。由于对于频繁被执行的程序,只需要在内存中有一份代码即可,所以代码区是可共享的(可以被别的程序调用)。为了防止程序意外地修改代码区的机器指令,通常代码区是只读的

¨          全局初始化数据区和静态数据区 包含已经被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。

¨          未初始化数据区 存储的是全局未初始化变量。

         以上谈的是存储时C语言的程序结构,下面看看运行时C语言的程序结构。

¨          代码区 该区的机器指令根据程序设计流程依次执行。代码区的指令包括操作码和要操作的对象(或对象地址引用,即寄存器间接寻址等)。如果操作对象是立即数,则将直接包含在代码中;如果操作对象是局部数据,则将在栈区分配空间,然后引用该数据地址;如果操作对象存在于BSS区和数据区,则在代码区中也将引用该数据地址。

¨          全局初始化数据区和静态数据区 只做一次初始化

¨          未初始化数据区 在运行时改变其值

¨          栈区 由编译器自动分配释放,存放函数的参数值、局部变量的值等等,其操作方式类似于数据结构中的栈。 这里简要谈一下C程序函数调用过程中栈框架的建立过程:

1)  第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址(目的是为了恢复现场)

2)  然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的

3)  然后是函数中的局部变量(注意静态变量是不入栈的)

4)  当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

¨          堆区 用于动态分配内存,位于未初始化数据区和栈区之间,一般由程序员分配和释放,若程序员不释放,程序结束时可能由OS回收。

          问题:为什么要专门开辟代码区

一个进程在运行的过程中,代码是根据流程一次执行的,只需要执行一次(当然跳转和递归也可使代码执行多次),然而程序可能会对数据进行多次访问(但没有必要为了对数据进行多次访问而多次访问代码),此时我们就有必要把代码区和数据区区分开来管理。

          一例程序

//main.cpp

int a = 0;  //a在全局初始化数据区

char *p1;   //p1在全局未初始化数据区

int main(int argc, char *argv[ ])

{

int b;    // b为局部变量,所以存储于栈区

char s[] = "abc";  // s为数组局部变量,存在于栈区

char *p2;  // p2为局部变量,所以存储于栈区

char *p3 = "123456";  // 123456\0在常量区(已初始化数据区),p3在栈上。

static int c =0; // c存储于静态数据区(静态数据区和全局初始化区同在一个区域)

p1 = (char *)malloc(10);  // 系统动态分配得来的10和20字节的区域就在堆区

p2 = (char *)malloc(20);  

free(p1);

free(p2);

return 0;

}

 

2、内存的分配(内存的申请)

1)  申请方式

¨      Stack(静态分配):静态对象是有名字的变量,可以直接对其进行操作。由系统自动分配内存。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间。

¨      Heap(动态分配):动态对象是没有名字的变量,需要通过指针间接地对它进行操作。需要程序员自己申请内存,并指明大小。

在C中malloc函数,如p1 = (char *)malloc(10);

在C++中用new运算符,如p2 = new char[20];   //(char *)malloc(20);

分配堆空间之后,p1、p2得到所分配堆空间首地址,将会指向堆空间。

2)  申请后系统的响应

¨      栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出

¨      堆:首先应该知道操作系统有一个记录空闲内存地址的链表当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

其次,大多数系统,会在这块内存空间中的首地址处记录本次分配的大小这样,代码中的delete语句才能正确的释放本内存空间。

此外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

 

3) 申请大小的限制

栈:在Windows下,栈是高地址向低地址扩展的数据结构是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的

在WINDOWS下,栈的大小是2M(也有的说是1M,总之它是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是低地址向高地址扩展的数据结构是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。

堆的大小受限于计算机系统中有效的虚拟内存,由此可见,堆获得的空间很灵活。

但由程序员操作的过程中容易发生内存泄露,也极易产生内存空间的不连续,即内存碎片(频繁使用malloc和free(new和delete)的结果)。

4)     申请效率的比较

¨      栈由系统自动分配,速度较快,但程序员是无法控制的。

¨      堆是由malloc或者new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

5) 分配效率的比较

       栈是机器提供的数据结构,机器会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令。

堆则是C函数库提供的,它的机制很复杂。首先应该知道操作系统有一个记录空闲内存地址的链表当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

此外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

可见,堆得存取效率和栈比较起来要低得多。

3、下面再给出一个数据存储区域的实例

#include <stdio.h>

#include <malloc.h>

#include <unistd.h>

#include <alloca.h>

 

extern void afunc(void);  //声明afunc()函数

extern etext, edata, end;  //声明三个外部变量

 

int bss_var; // 未初始化全局数据存储在BSS区

int data_var = 42; // 初始化全局数据存储在数据区

 

// 定义了一个宏,用于打印地址

#define SHW_ADR(ID, I) printf("the %8s\t is at adr:%8x\n", ID, &I);

 

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);  // 查看代码段main函数位置

       SHW_ADR("afunc", afunc);  // 查看代码段afunc函数位置

 

       printf("\NBSS Location:\n");

       SHW_ADR("bss_var", bss_var);  // 查看BSS段变量位置

 

       printf("\NDATA Location:\n");

       SHW_ADR("data_var", data_var);  // 查看数据段变量位置

 

       printf("\nSTACK Location:\n");

 

       afunc();

 

       p = (char *)alloca(32);    // 从栈中分配空间

       if(p != NULL){

              SHW_ADR("start", p);   // 打印栈空间的起始位置

              SHW_ADR("end", p+31); // 打印栈空间的结束位置

       }

 

       b = (char *)malloc(32*sizeof(char)); // 从堆中分配空间

       nb = (char *)malloc(16*sizeof(char)); // 从堆中分配空间

 

       printf("\NHEAP Location:\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);          // 打印栈中字符指针变量b的存储位置

       SHW_ADR("nb", nb);  // 打印栈中字符指针变量nb的存储位置

 

       free(b);  // 释放申请的堆空间

       free(nb);  // 释放申请的堆空间

      

return 0;

}

 

void afunc(void)

{

       static int long level = 0; // 静态数据存储在数据段中

       int stack_var;  // 局部变量,存储在栈区

       if(++level == 5){

              return;

       }

       printf("stack_var is at:%p\n", &stack_var); // 打印局部变量的地址

       afunc(); // 递归执行afunc函数

}

4、介绍几个内存管理函数

1)     Malloc/free函数

原型:extern void *malloc(size_t  num_bytes);  

头文件:include <stdlib.h> 

功能:分配长度为num_bytes字节的内存块   

返回值:如果分配成功则返回指向被分配内存首地址的指针,否则返回空指针NULL。

说明:

¨          该函数返回为void型指针,因此必要时要进行类型转换。   

¨          当内存不再使用时,应使用free()函数将内存块释放。   

问题:为什么不再使用时,要用free()释放掉所申请的内存?

¨          由于内存区域总是有限的,不能无限制地分配下去。

¨          程序应该尽可能地去节省资源,当申请的堆空间不再使用时,应该释放掉,交由其它进程来使用。

注意:不能用free()来释放非malloc(), calloc(), realloc()函数所创建的堆空间,否则会发生错误。

2)new/delete(在C++中)

       使用new/delete运算符实现内存管理比malloc/free函数更有优越性。它们的定义如下:

Static void* operator new(size_t sz);

Static void* operator delete(void* p);

先看一段C++代码:

void test(void)

{

//申请一个sizeof(obj)大小的一块动态内存,并把头指针赋值给obj类型的指针变量a

       obj *a = new obj;

       delete a; //清除并且释放所申请的内存

}

下面通过一段代码具体介绍一下new/delete的用法:

Class A

{

       Public:

              A() { count << “A is here!” << endl; }   //构造函数

              ~A() { count << “A is here!” << endl; }  //析构函数

       Private:

              Int I;

};

A* pA = new A;   // 调用new运算符申请空间

delete pA;     // 删除pA

       其中,语句new A完成了一下两个功能:

A.      调用new运算符,在堆上分配一个sizeof(A)大小的内存空间。

B.     调用构造函数A(),在所分配的内存空间上初始化对象。

语句delete pA完成的是相反的两件事:

A.      调用析构函数~A(),销毁对象。

B.     调用运算符delete,释放内存。

注意:

¨      使用new比使用malloc()有以下优点

A.      New 自动计算要分配给对象的内存空间大小,不使用sizeof运算符,这样一来简单,而来可以避免错误。

B.     自动地返回正确的指针类型,不用进行强制类型转换。

C.     用构造函数给分配的对象进行初始化。

¨      使用malloc函数和new分配内存的时候,本身并没有对这块内存空间做清零等任何工作。因此,申请内存空间后,其返回的新分配的空间是没有用零填充的,程序员须使用memset()函数来初始化内存。

3)  realloc函数(更改已经配置的内存空间)

头文件:include <stdlib.h> 

函数定义:

       void *realloc(void *ptr, size_t size)

参数ptr为先前由malloc、calloc和realloc所返回的内存指针,而参数size为新配置的内存大小。

       realloc函数用来从堆上分配内存,当需要扩大一块内存空间时,realloc()试图直接从堆上当前内存段后面的字节中获得更多的内存空间:

¨      如果能够分配成功,则返回指向这块新内存空间的首地址,而将原来的指针(realloc函数的参数指针)指向的空间释放掉;

¨      如果当前内存段后面的空闲字节不够,那么就使用堆上第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,而将原来的数据块释放掉;

¨      如果内存不足,重新申请空间失败,则返回NULL,此时原来的指针(realloc函数的参数指针)仍有效。

#include <stdio.h>

#include <stdlib.h>

 

int main (int argc, char* argv[], char* envp[])                                          //主函数

{

  int input;

  int n;

  int *numbers1;

  int *numbers2;

  numbers1 = NULL;

 

  if((numbers2 = (int *)malloc(5*sizeof(int))) == NULL)          //numbers2指针申请空间

{

printf("malloc memory unsuccessful");

//free(numbers2);

//numbers2=NULL;

exit(1);

  }

  for (n=0; n<5; n++)                                                                      //初始化(0-4)

  {

      *(numbers2+n)=n;

      printf("numbers2's data: %d\n", *(numbers2+n));     //把0-4打印出来

  }

 

  printf("Enter an integer value you want to remalloc ( enter 0 to stop)\n"); //新申请空间大小 

  scanf ("%d", &input);

    

  numbers1 = (int *)realloc(numbers2, (input+5)*sizeof(int));                     // 重新申请空间

  if (numbers1 == NULL)

{

         printf("Error (re)allocating memory");

         exit (1);

  }

 

for(n=0;n<5;n++)                          // 这5个数是从numbers2(原指针空间)拷贝而来

{

printf("the numbers1s's data copy from numbers2: %d\n", *(numbers1+n));

}

 

 for(n=0; n<input; n++)                                             // 新数据初始化(0-input)

 {

      *(numbers1+5+n) = n*2;

      printf ("nummber1's new data: %d\n", *(numbers1+5+n));  // numbers1++;

 }

 

  printf("\n");

  free(numbers1);                                                                           // 释放numbers1

  numbers1 = NULL;

//  free(numbers2);                 // 不能再释放numbers2,因为number2早已被系统自动释放

  return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值