理解内存管理的本质

来源:互联网 发布:windows 商业版 编辑:程序博客网 时间:2024/06/04 23:30

理解内存管理的本质 

         看到很多使用C/C++的朋友都在内存管理上跌倒,不断的苦苦挣扎。究其原因,并非全是因为粗心导致的错误,而是在内存管理的理解上有偏差导致的混乱。个人认为关于内存的使用,最重要的一点就是认识到内存分配和释放操作的本质其实是对一片内存地址范围的所有权的分配。下面会详细阐述一下我个人的理解,希望对需要的朋友,尤其是新人会有些帮助。

      作为C/C++开发人员来说,语言本身为程序员管理内存提供了极强的灵活性。同时也带来了一些问题。程序员必须非常谨慎的正确的处理指针,管理好内存。从程序员的角度来看,内存使用的最主要的形式有两种:堆和堆栈。下面有个小程序,通过它来帮助我们理解这种所有权的变化。

 

      首先来看看堆栈中的内存管理。大家都知道函数中的局部变量是自动分配的。不需要程序员主动去new/delete。就是因为对于函数内部定义的局部变量,编译器会生成为这些变量分配空间的代码。我们不妨看看编译器对ShowMsgFromStack函数中szInfo变量是怎么管理的。注意,szInfo总共是63个字节。

 

      上面是release模式下生成的代码。我们看到ShowMsgFromStack函数一开始就有sub esp,64的代码。这就是编译器在堆栈中为szInfo预留的64个字节的空间(尽管定义的是63个字节,但是会考虑到堆栈指针对齐的问题),也就是说,从现在开始这64个字节就是交给程序员当作szInfo来处理。系统保证不会把这64个字节分配给其他函数用。同样的,在函数结尾处有add esp,64的一行代码。这是函数要结束了,所以系统要收回这64个字节的所有权。从此以后,这64个字节就由系统管理,想分给谁就分给谁。总之,程序员不应该再使用它了。

      注意重点:这64字节的内存是早就存在的,不是因为szInfo而新产生的,也不会因为szInfo的生命期结束而消失。这里所谓的分配和释放无非是改一下堆栈指针的位置,使得所有权发生变化而已。理论上,只要有个指针指向这64个字节,就能够访问和修改(不一定合法)这片内存的数据。所以我们经常说野指针很危险。

      如果明确了内存使用中的所有权这个概念,那么面对下面几个初学者常见的迷思就不难想明白了:

      1.总听说不能在函数中返回局部变量的地址,那为什么下面的代码运行正常?

 

 

 

 

       这段代码在VC6的Debug和Release模式下运行这段代码都能看到会显示bad usage。看起来似乎一切正常。然而,真的正常吗?把buf[3200]改成buf[32]试试,不正常了吧。其解释就如同本文要强调的:指针理论上可以指向任何一个地址,但是只有合法的指向一个所有权归你的地址才是安全的。否则的话,系统有可能将这块内存挪作他用。如果在挪用前,你使用了那就看起来是正常的,如上面这个错误代码一样;如果在系统将它挪作他用了,你再去用它,那就出错了,buf大小改为32后出现的就是这情况。实际上那32字节的内存被printf函数占用了。

 

 

      2.为什么有时候指针指向被释放了的内存,也照样能访问,能读能写。有时候一访问就出错,报什么0xC0000005错误。

      这是同样的道理。释放了的内存,如果还没有被另外分配给别的地方使用,那么就是一块普通的内存,你可以随便用。直到随机的发生所有权的变更后,你去读它写它,别人也读它写它,结果就是两边都不讨好。所以请记住,不要这么做。至于有时候能访问,有时候访问又异常,那是因为4G的私有内存地址空间中,有些区域是提交了的,有些是未提交的。如果指针是指向的未提交的内存,那么立即就会被系统捕获到。而如果指向的是已提交的内存,那么依赖于其所有权。

 

      本来是准备结合调试器看看现象,说说堆栈和堆对于内存分配的管理的,但是内容确实太多了,工作又突然忙碌了起来,所以先写到这吧。堆的管理比堆栈要复杂一些,但是关于所有权的概念还是一致的,以后有空了再好好谈谈。

 

      总之呢,请永远牢记不要访问不归你所有的内存,否则的话,系统很生气,后果很严重!

view plaincopy to clipboardprint?
  1. #include <stdio.h>  
  2. #include <string.h>  
  3.   
  4. char* ReturnString(const char* szMsg)  
  5. {  
  6.     char buf[3200] = {0};  
  7.     strcpy(buf, szMsg);  
  8.     return buf;  
  9. }  
  10.   
  11. int main(void)  
  12. {  
  13.     char* p = ReturnString("bad usage");  
  14.     printf("%s/n", p);  
  15.     return 0;  
  16. }  

view plaincopy to clipboardprint?
  1. ;   COMDAT ?ShowMsgFromStack@@YAXPBD@Z  
  2. _TEXT   SEGMENT  
  3. _cszMsg$ = 8  
  4. _szInfo$ = -64  
  5. ?ShowMsgFromStack@@YAXPBD@Z PROC NEAR           ; ShowMsgFromStack, COMDAT  
  6.   
  7. ; 12   : {  
  8.   
  9.   00000 83 ec 40     sub     esp, 64            ; 00000040H  
  10.   00003 56       push    esi  
  11.   00004 57       push    edi  
  12.   
  13. ; 13   :    char szInfo[64] = {0};  
  14.   
  15.   00005 b9 0f 00 00 00   mov     ecx, 15            ; 0000000fH  
  16.   0000a 33 c0        xor     eax, eax  
  17.   0000c 8d 7c 24 09  lea     edi, DWORD PTR _szInfo$[esp+73]  
  18.   00010 c6 44 24 08 00   mov     BYTE PTR _szInfo$[esp+72], 0  
  19.   00015 f3 ab        rep stosd  
  20.   00017 66 ab        stosw  
  21.   00019 aa       stosb  
  22.   
  23. ; 14   :    strcpy(szInfo, cszMsg);  
  24.   
  25.   0001a 8b 7c 24 4c  mov     edi, DWORD PTR _cszMsg$[esp+68]  
  26.   0001e 83 c9 ff     or  ecx, -1  
  27.   00021 33 c0        xor     eax, eax  
  28.   00023 8d 54 24 08  lea     edx, DWORD PTR _szInfo$[esp+72]  
  29.   00027 f2 ae        repne scasb  
  30.   00029 f7 d1        not     ecx  
  31.   0002b 2b f9        sub     edi, ecx  
  32.   0002d 8b c1        mov     eax, ecx  
  33.   0002f 8b f7        mov     esi, edi  
  34.   00031 8b fa        mov     edi, edx  
  35.   00033 c1 e9 02     shr     ecx, 2  
  36.   00036 f3 a5        rep movsd  
  37.   00038 8b c8        mov     ecx, eax  
  38.   0003a 83 e1 03     and     ecx, 3  
  39.   0003d f3 a4        rep movsb  
  40.   
  41. ; 15   :    printf("ShowMsg from stack: %s/n", szInfo);  
  42.   
  43.   0003f 8d 4c 24 08  lea     ecx, DWORD PTR _szInfo$[esp+72]  
  44.   00043 51       push    ecx  
  45.   00044 68 00 00 00 00   push    OFFSET FLAT:??_C@_0BI@LOFA@ShowMsg?5from?5stack?3?5?$CFs?6?$AA@ ; `string'  
  46.   00049 e8 00 00 00 00   call    _printf  
  47.   0004e 83 c4 08     add     esp, 8  
  48.   00051 5f       pop     edi  
  49.   00052 5e       pop     esi  
  50.   
  51. ; 16   : }  
  52.   
  53.   00053 83 c4 40     add     esp, 64            ; 00000040H  
  54.   00056 c3       ret     0  
  55. ?ShowMsgFromStack@@YAXPBD@Z ENDP            ; ShowMsgFromStack  

view plaincopy to clipboardprint?
  1. /****************************************************/   
  2. /* 程序:Lesson_3                                    */   
  3. /* 功能:演示说明内存管理的本质                            */   
  4. /* 作者:coding (http://blog.csdn.net/coding_hello)  */   
  5. /* 日期:2009-03-01                                  */   
  6. /****************************************************/  
  7.   
  8. #include <stdio.h>  
  9. #include <string.h>  
  10.   
  11. void ShowMsgFromStack(const char* cszMsg)  
  12. {  
  13.     char szInfo[63] = {0};  
  14.     strcpy(szInfo, cszMsg);  
  15.     printf("ShowMsg from stack: %s/n", szInfo);  
  16. }  
  17.   
  18. void ShowMsgFromHeap(const char* cszMsg)  
  19. {  
  20.     char* pInfo = new char[63];  
  21.     strcpy(pInfo, cszMsg);  
  22.     printf("ShowMsg from heap: %s/n", pInfo);  
  23.     delete pInfo;  
  24. }  
  25.   
  26. int main()  
  27. {  
  28.     ShowMsgFromStack("http://blog.csdn.net/coding_hello");  
  29.     ShowMsgFromHeap("http://blog.csdn.net/coding_hello");  
  30.   
  31.     return 0;  
  32. }  

原创粉丝点击