理解内存管理的本质
看到很多使用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的私有内存地址空间中,有些区域是提交了的,有些是未提交的。如果指针是指向的未提交的内存,那么立即就会被系统捕获到。而如果指向的是已提交的内存,那么依赖于其所有权。
本来是准备结合调试器看看现象,说说堆栈和堆对于内存分配的管理的,但是内容确实太多了,工作又突然忙碌了起来,所以先写到这吧。堆的管理比堆栈要复杂一些,但是关于所有权的概念还是一致的,以后有空了再好好谈谈。
总之呢,请永远牢记不要访问不归你所有的内存,否则的话,系统很生气,后果很严重!
- #include <stdio.h>
- #include <string.h>
- char* ReturnString(const char* szMsg)
- {
- char buf[3200] = {0};
- strcpy(buf, szMsg);
- return buf;
- }
- int main(void)
- {
- char* p = ReturnString("bad usage");
- printf("%s/n", p);
- return 0;
- }
- ; COMDAT ?ShowMsgFromStack@@YAXPBD@Z
- _TEXT SEGMENT
- _cszMsg$ = 8
- _szInfo$ = -64
- ?ShowMsgFromStack@@YAXPBD@Z PROC NEAR ; ShowMsgFromStack, COMDAT
- ; 12 : {
- 00000 83 ec 40 sub esp, 64 ; 00000040H
- 00003 56 push esi
- 00004 57 push edi
- ; 13 : char szInfo[64] = {0};
- 00005 b9 0f 00 00 00 mov ecx, 15 ; 0000000fH
- 0000a 33 c0 xor eax, eax
- 0000c 8d 7c 24 09 lea edi, DWORD PTR _szInfo$[esp+73]
- 00010 c6 44 24 08 00 mov BYTE PTR _szInfo$[esp+72], 0
- 00015 f3 ab rep stosd
- 00017 66 ab stosw
- 00019 aa stosb
- ; 14 : strcpy(szInfo, cszMsg);
- 0001a 8b 7c 24 4c mov edi, DWORD PTR _cszMsg$[esp+68]
- 0001e 83 c9 ff or ecx, -1
- 00021 33 c0 xor eax, eax
- 00023 8d 54 24 08 lea edx, DWORD PTR _szInfo$[esp+72]
- 00027 f2 ae repne scasb
- 00029 f7 d1 not ecx
- 0002b 2b f9 sub edi, ecx
- 0002d 8b c1 mov eax, ecx
- 0002f 8b f7 mov esi, edi
- 00031 8b fa mov edi, edx
- 00033 c1 e9 02 shr ecx, 2
- 00036 f3 a5 rep movsd
- 00038 8b c8 mov ecx, eax
- 0003a 83 e1 03 and ecx, 3
- 0003d f3 a4 rep movsb
- ; 15 : printf("ShowMsg from stack: %s/n", szInfo);
- 0003f 8d 4c 24 08 lea ecx, DWORD PTR _szInfo$[esp+72]
- 00043 51 push ecx
- 00044 68 00 00 00 00 push OFFSET FLAT:??_C@_0BI@LOFA@ShowMsg?5from?5stack?3?5?$CFs?6?$AA@ ; `string'
- 00049 e8 00 00 00 00 call _printf
- 0004e 83 c4 08 add esp, 8
- 00051 5f pop edi
- 00052 5e pop esi
- ; 16 : }
- 00053 83 c4 40 add esp, 64 ; 00000040H
- 00056 c3 ret 0
- ?ShowMsgFromStack@@YAXPBD@Z ENDP ; ShowMsgFromStack
- /****************************************************/
- /* 程序:Lesson_3 */
- /* 功能:演示说明内存管理的本质 */
- /* 作者:coding (http://blog.youkuaiyun.com/coding_hello) */
- /* 日期:2009-03-01 */
- /****************************************************/
- #include <stdio.h>
- #include <string.h>
- void ShowMsgFromStack(const char* cszMsg)
- {
- char szInfo[63] = {0};
- strcpy(szInfo, cszMsg);
- printf("ShowMsg from stack: %s/n", szInfo);
- }
- void ShowMsgFromHeap(const char* cszMsg)
- {
- char* pInfo = new char[63];
- strcpy(pInfo, cszMsg);
- printf("ShowMsg from heap: %s/n", pInfo);
- delete pInfo;
- }
- int main()
- {
- ShowMsgFromStack("http://blog.youkuaiyun.com/coding_hello");
- ShowMsgFromHeap("http://blog.youkuaiyun.com/coding_hello");
- return 0;
- }