C++程序内存分配问题

from:http://hi.baidu.com/sxnuwhui/item/bf4b835d4dcc474b4eff20a5

一、C++程序内存分配

1) 在栈上创建。在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,一般使用寄存器来存取,效率很高,但是分配的内存容量有限。

2) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete来释放内存。动态内存的生存期由程序员自己决定,使用非常灵活。

3) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

4) 文字常量分配在文字常量区,程序结束后由系统释放。

5)程序代码区。 存放函数体的二进制代码。

经典实例:

Code

#include <string>

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

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

void main()

{

    int b;//栈

    char s[]="abc";   //栈

    char *p2;         //栈

    char *p3="123456";   //123456\0在常量区,p3在栈上。

    static int c=0;   //全局(静态)初始化区

    p1 = (char*)malloc(10);

    p2 = (char*)malloc(20);   //分配得来得10和20字节的区域就在堆区。

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

}

二、三种内存对象的比较

  栈对象的优势是在适当的时候自动生成,又在适当的时候自动销毁,不需要程序员操心;而且栈对象的创建速度一般较堆对象快,因为分配堆对象时,会调用operator new操作,operator new会采用某种内存空间搜索算法,而该搜索过程可能是很费时间的,产生栈对象则没有这么麻烦,它仅仅需要移动栈顶指针就可以了。但是要注意的是,通常栈空间容量比较小,一般是1MB~2MB,所以体积比较大的对象不适合在栈中分配。特别要注意递归函数中最好不要使用栈对象,因为随着递归调用深度的增加,所需的栈空间也会线性增加,当所需栈空间不够时,便会导致栈溢出,这样就会产生运行时错误。

  堆对象创建和销毁都要由程序员负责,所以,如果处理不好,就会发生内存问题。如果分配了堆对象,却忘记了释放,就会产生内存泄漏;而如 果已释放了对象,却没有将相应的指针置为NULL,该指针就是所谓的“悬挂指针”,再度使用此指针时,就会出现非法访问,严重时就导致程序崩溃。但是高效的使用堆对象也可以大大的提高代码质量。比如,我们需要创建一个大对象,且需要被多个函数所访问,那么这个时候创建一个堆对象无疑是良好的选择,因为我们通过在各个函数之间传递这个堆对象的指针,便可以实现对该对象的共享,相比整个对象的传递,大大的降低了对象的拷贝时间。另外,相比于栈空间,堆的容量要大得多。实际上,当物理内存不够时,如果这时还需要生成新的堆对象,通常不会产生运行时错误,而是系统会使用虚拟内存来扩展实际的物理内存。

  静态存储区。所有的静态对象、全局对象都于静态存储区分配。关于全局对象,是在main()函数执行前就分配好了的。其实,在main()函数中的显示代 码执行之前,会调用一个由编译器生成的_main()函数,而_main()函数会进行所有全局对象的的构造及初始化工作。而在main()函数结束之 前,会调用由编译器生成的exit函数,来释放所有的全局对象。比如下面的代码:

void main(void)

{

… …// 显式代码

}


实际上,被转化成这样:

void main(void)

{

_main(); //隐式代码,由编译器产生,用以构造所有全局对象

… … // 显式代码

… …

exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象

}


  除了全局静态对象,还有局部静态对象通和class的静态成员,局部静态对象是在函数中定义的,就像栈对象一样,只不过,其前面多了个static关键字。局部静态对象的生命期是从其所在函数第一次被调用,更确切地说,是当第一次执行到该静态对象的声明代码时,产生该静态局部对象,直到整个程序结束时,才销毁该对象。class的静态成员的生命周期是该class的第一次调用到程序的结束。

三 函数调用与堆栈

1)编译器一般使用栈来存放函数的参数,局部变量等来实现函数调用。有时候函数有嵌套调用,这个时候栈中会有多个函数的信息,每个函数占用一个连续的区域。一个函数占用的区域被称作帧(frame)。同时栈是线程独立的,每个线程都有自己的栈。例如下面简单的函数调用:

另外函数堆栈的清理方式决定了当函数调用结束时由调用函数或被调用函数来清理函数帧,在VC中对函数栈的清理方式由两种:


参数传递顺序 谁负责清理参数占用的堆栈
__stdcall 从右到左 被调函数
__cdecl 从右到左 调用者


2) 有了上面的知识为铺垫,我们下面细看一个函数的调用时堆栈的变化:

代码如下:

Code

int Add(int x, int y)

{

    return x + y;

}

void main()

{

    int *pi = new int(10);

    int *pj = new int(20);

    int result = 0;

    result = Add(*pi,*pj);

    delete pi;

    delete pj;

}

对上面的代码,我们分为四步,当然我们只画出了我们的代码对堆栈的影响,其他的我们假设它们不存在

第一,int *pi = new int(10);   int *pj = new int(20);   int result = 0;堆栈变化如下:

第二,Add(*pi,*pj);堆栈如下:

第三,将Add的结果给result,堆栈如下:

第四,delete pi;    delete pj; 堆栈如下:

第五,当main()退出后,堆栈如下,等同于main执行前

1)在递归的时候还是多用new

2)栈内存溢出多出现在递归的时候栈空间不够

3)大块数据块多用堆

4)每个线程有自己的栈

/*********************************************************************************************************************************/

内存基本上分为静态存储区、堆区和栈区三大部分。

程序运行时,特别要注意的是内存的分配。下面介绍C++程序设计中的内存分配

一、内存基本构成

可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。

静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。

栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。

二、三者之间的区别

我们通过代码段来看看对这样的三部分内存需要怎样的操作和不同,以及应该注意怎样的地方。

例一:静态存储区与栈区

  
  
  1. char* p = “Hello World1”;  
  2. char a[] = “Hello World2”;  
  3. p[2] = ‘A’;  
  4. a[2] = ‘A’;  
  5. char* p1 = “Hello World1;” 

这个程序是有错误的,错误发生在p[2] = ‘A’这行代码处,为什么呢,是变量p和变量数组a都存在于栈区的(任何临时变量都是处于栈区的,包括在main()函数中定义的变量)。但是,数据“Hello World1”和数据“Hello World2”是存储于不同的区域的。

因为数据“Hello World2”存在于数组中,所以,此数据存储于栈区,对它修改是没有任何问题的。因为指针变量p仅仅能够存储某个存储空间的地址,数据“Hello World1”为字符串常量,所以存储在静态存储区。虽然通过p[2]可以访问到静态存储区中的第三个数据单元,即字符‘l’所在的存储的单元。但是因为数据“Hello World1”为字符串常量,不可以改变,所以在程序运行时,会报告内存错误。并且,如果此时对p和p1输出的时候会发现p和p1里面保存的地址是完全相同的。换句话说,在数据区只保留一份相同的数据。

例二:栈区与堆区

  
  
  1. char* f1()  
  2. {  
  3. char* p = NULL;  
  4. char a;  
  5. p = &a;  
  6. return p;  
  7. }  
  8. char* f2()  
  9. {  
  10. char* p = NULL:  
  11. p =(char*) new char[4];  
  12. return p;  

这两个函数都是将某个存储空间的地址返回,二者有何区别呢?f1()函数虽然返回的是一个存储空间,但是此空间为临时空间。也就是说,此空间只有短暂的生命周期,它的生命周期在函数f1()调用结束时,也就失去了它的生命价值,即:此空间被释放掉。所以,当调用f1()函数时,如果程序中有下面的语句:

  
  
  1. char* p ;  
  2. p = f1();  
  3. *p = ‘a’; 

此时,编译并不会报告错误,但是在程序运行时,会发生异常错误。因为,你对不应该操作的内存(即,已经释放掉的存储空间)进行了操作。但是,相比之下,f2()函数不会有任何问题。因为,new这个命令是在堆中申请存储空间,一旦申请成功,除非你将其delete或者程序终结,这块内存将一直存在。也可以这样理解,堆内存是共享单元,能够被多个函数共同访问。如果你需要有多个数据返回却苦无办法,堆内存将是一个很好的选择。但是一定要避免下面的事情发生:

  
  
  1. void f()  
  2. {  
  3. …  
  4. char * p;  
  5. p = (char*)new char[100];  
  6. …  

这个程序做了一件很无意义并且会带来很大危害的事情。因为,虽然申请了堆内存,p保存了堆内存的首地址。但是,此变量是临时变量,当函数调用结束时p变量消失。也就是说,再也没有变量存储这块堆内存的首地址,我们将永远无法再使用那块堆内存了。

但是,这块堆内存却一直标识被你所使用(因为没有到程序结束,你也没有将其delete,所以这块堆内存一直被标识拥有者是当前您的程序),进而其他进程或程序无法使用。我们将这种不道德的“流氓行为”(我们不用,却也不让别人使用)称为内存泄漏。这是我们C++程序员的大忌!!请大家一定要避免这件事情的发生。

总之,对于堆区、栈区和静态存储区它们之间最大的不同在于,栈的生命周期很短暂。但是堆区和静态存储区的生命周期相当于与程序的生命同时存在(如果您不在程序运行中间将堆内存delete的话),我们将这种变量或数据成为全局变量或数据。但是,对于堆区的内存空间使用更加灵活,因为它允许你在不需要它的时候,随时将它释放掉,而静态存储区将一直存在于程序的整个生命周期中。

/********************************************************************************************************************************************************/

from: http://www.cnblogs.com/rusty/archive/2011/03/21/1990667.html

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

1、栈区(stack)——由编译器自动分配释放,在不需要的时候自动清除。用于存放函数的参数、局部变量等。操作方式类似数据结构中的栈(后进先出)。

2、堆区(heap)——一般由程序员分配释放,若程序员分配后不释放,程序结束后可能由OS回收。不同于数据结构中的堆,分配方式有些类似链表。

3、全局区(静态区)——全局变量和静态变量存储在这里。程序结束后由系统释放。在以前到C语言中,全局变量又细分为初始化的(DATA段)和未初始化到(BSS段),在C++里已经没有这个区分了,它们共同占用同一块内存区。

4、常量存储区——常量字符串就存放在这里。一般不允许修改。程序结束后由系统释放。

5、代码区——存放函数体的二进制代码。

示意图如下:

|----------------------|     高地址
|     栈区(Statk)    | -->向下增长
|----------------------|
|     堆区(Heap)    | -->向上增长
|----------------------|
| 未初始化(BSS) |
|----------------------|
|   初始化(Data)   |
|----------------------|
|    常量存储区    |

|----------------------|

|   正文段(Text)   |
|----------------------|    低地址

附上另一副图:

二、一段经典的例子程序,帮助理解

复制代码
   
   
1 // main.c 2 #include < string .h > 3 #include < stdlib.h > 4 int a = 0 ; // 全局初始化区 5 char * p1; // 全局未初始化区 6 int main() 7 { 8 int b = 0 ; // 9 char s[] = " abc " ; // 10 char * p2; // 11 char * p3 = " 123456 " ; // 123456\0在常量区,p3在栈上 12 static int c = 0 ; // 全局初始化区 13 p1 = ( char * )malloc( 10 ); 14 p2 = ( char * )malloc( 20 ); // 分配得到到空间在堆区 15 strcpy(p1, " 123456 " ); // 123456\0放在常量区 16                        // 编译器可能会将它与p3所指向的123456\0优化成一个地方 17 return 0 ; 18 }
复制代码
Ubuntu下用gcc 生成汇编看看,命令:
   
   
gcc -S main . c
打开目录下到main.s,汇编代码如下:
复制代码
   
   
1 .file " main.c " 2 .globl a 3 .bss                   ;大概看出这是BSS段声明 4 .align 4 5 .type a, @object 6 .size a, 4 7  a: 8 .zero 4 9 .comm p1, 4 , 4         ;这里大概就是DATA段 10 .section .rodata 11 . LC1: 12 .string " 123456 "     ;常量存储区 13 . LC0: 14 .string " abc "        ;栈区 15 .text                  ;代码段 16 .globl main 17 .type main, @function 18 main: 19 pushl %ebp 20 movl %esp, %ebp     ;保存esp现场在ebp中,ebp保存在栈中。Linux下mov a,b是把a赋值给b,与Win下相反 21 andl $- 16 , %esp     ;这个貌似是为了对齐神马的 22 subl $ 32 , %esp      ;分配栈区,用于存放局部变量等。栈区向下增长,因此是减 23 movl $ 0 , 28 (%esp)   ;esp+28,自然是int b = 0;这句 24 movl .LC0, %eax 25 movl %eax, 24 (%esp) ;char s[] = "abc"; 26 movl $.LC1, 16 (%esp);char *p3 = "123456"这句,esp+20是p2,char *p2这句被优化到后面30-32去了 27 movl $ 10 , (%esp) 28 call malloc 29 movl %eax, p1       ;p1 = (char *)malloc(10); 30 movl $ 20 , (%esp) 31 call malloc 32 movl %eax, 20 (%esp) ;p2 = (char *)malloc(20); 33 movl $.LC1, %edx 34 movl p1, %eax 35 movl $ 7 , 8 (%esp) 36 movl %edx, 4 (%esp) 37 movl %eax, (%esp) 38 call memcpy         ;strcpy(p1,"123456")这句,“123456\0”,用的LC1,和上面用的一个 39 movl $ 0 , %eax       ;return 0; 40 leave 41 ret 42 .size main, .-main 43 .local c. 1974 44 .comm c. 1974 , 4 , 4 45 .ident " GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 " 46 .section .note.GNU-stack, "" ,@progbits
复制代码

用空再试试-o -o2神马的。还有Win下到反汇编。

Linux下的汇编教程看这里

三、回忆几个题

1、常量存储区的优化

复制代码
   
   
1 #include < stdio.h > 2 int main() 3 { 4 char str1[] = " hello world " ; 5 char str2[] = " hello world " ; 6 char * str3 = " hello world " ; 7 char * str4 = " hello world " ; 8 if (str1 == str2) 9 printf( " str1 and str2 are same.\n " ); 10 else 11 printf( " str1 and str2 are not same.\n " ); 12 13 if (str3 == str4) 14 printf( " str3 and str4 are same.\n " ); 15 else 16 printf( " str3 and str4 are not same.\n " ); 17 return 0 ; 18 } //str1 and str2 are not same. //str3 and str4 are same.
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值