??xml version="1.0" encoding="utf-8" standalone="yes"?>
]]>
伟大的Bill Gates 曄pQ?br>
640K ought to be enough for everybody ?Bill Gates 1981
E序员们l常~写内存理E序Q往往提心吊胆。如果不惌P唯一的解军_法就是发现所有潜伏的地雷q且排除它们Q躲是躲不了的。本文的内容比一般教U书的要深入得多Q读者需l心阅读Q做到真正地通晓内存理?br>
1、内存分配方?/strong>
内存分配方式有三U:
Q?Q从静态存储区域分配。内存在E序~译的时候就已经分配好,q块内存在程序的整个q行期间都存在。例如全局变量Qstatic变量?br>
Q?Q在栈上创徏。在执行函数Ӟ函数内局部变量的存储单元都可以在栈上创徏Q函数执行结束时q些存储单元自动被释放。栈内存分配q算内置于处理器的指令集中,效率很高Q但是分配的内存定w有限?br>
Q?Q?从堆上分配,亦称动态内存分配。程序在q行的时候用malloc或new甌L多少的内存,E序员自p责在何时用free或delete释放内存。动态内存的生存期由我们军_Q用非常灵z,但问题也最多?br>
2、常见的内存错误及其对策
发生内存错误是g非常ȝ的事情。编译器不能自动发现q些错误Q通常是在E序q行时才能捕捉到。而这些错误大多没有明昄症状Q时隐时玎ͼ增加了改错的隑ֺ。有时用h气冲冲地把你找来,E序却没有发生Q何问题,你一赎ͼ错误又发作了?常见的内存错误及其对{如下:
* 内存分配未成功,却用了它?br>
~程新手常犯q种错误Q因Z们没有意识到内存分配会不成功。常用解军_法是Q在使用内存之前查指针是否ؓNULL。如果指针p是函数的参数Q那么在函数的入口处用assert(p!=NULL)q行
查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)q行防错处理?br>
* 内存分配虽然成功Q但是尚未初始化引用它?br>
犯这U错误主要有两个起因Q一是没有初始化的观念;二是误以为内存的~省初值全为零Q导致引用初值错误(例如数组Q?
内存的缺省初值究竟是什么ƈ没有l一的标准,管有些时候ؓ零|我们宁可信其无不可信其有。所以无论用何种方式创徏数组Q都别忘了赋初|即便是赋零?
也不可省略,不要嫌麻烦?br>
* 内存分配成功q且已经初始化,但操作越q了内存的边界?br>
例如在用数l时l常发生下标“?”或?#8220;?”的操作。特别是在for循环语句中,循环ơ数很容易搞错,D数组操作界?br>
* 忘记了释攑ֆ存,造成内存泄露?br>
含有q种错误的函数每被调用一ơ就丢失一块内存。刚开始时pȝ的内存充I你看不到错误。终有一ơ程序突然死掉,pȝ出现提示Q内存耗尽?br>
动态内存的甌与释攑ֿ配对,E序中malloc与free的用次C定要相同Q否则肯定有错误Qnew/delete同理Q?br>
* 释放了内存却l箋使用它?br>
有三U情况:
Q?Q程序中的对象调用关p过于复杂,实在难以搞清楚某个对象究竟是否已l释放了内存Q此时应该重新设计数据结构,从根本上解决对象理的乱局面?br>
Q?Q函数的return语句写错了,注意不要q回指向“栈内?#8221;?#8220;指针”或?#8220;引用”Q因内存在函Cl束时被自动销毁?br>
Q?Q用free或delete释放了内存后Q没有将指针讄为NULL。导致?#8220;野指?#8221;?br>
【规?】用malloc或new甌内存之后Q应该立x查指针值是否ؓNULL。防止用指针gؓNULL的内存?br>
【规?】不要忘Cؓ数组和动态内存赋初倹{防止将未被初始化的内存作ؓ叛_g用?br>
【规?】避免数l或指针的下标越界,特别要当心发?#8220;?”或?#8220;?”操作?br>
【规?】动态内存的甌与释攑ֿ配对,防止内存泄漏?br>
【规?】用free或delete释放了内存之后,立即指针设|ؓNULLQ防止?#8220;野指?#8221;?br>
3、指针与数组的对?/strong>
C++/CE序中,指针和数l在不少地方可以怺替换着用,让h产生一U错觉,以ؓ两者是{h的?br>
数组要么在静态存储区被创建(如全局数组Q,要么在栈上被创徏。数l名对应着Q而不是指向)一块内存,其地址与容量在生命期内保持不变Q只有数l的内容可以改变?br>
指针可以随时指向Lcd的内存块Q它的特征是“可变”Q所以我们常用指针来操作动态内存。指针远比数l灵z,但也更危险?br>
下面以字W串Z比较指针与数l的Ҏ?br>
3.1 修改内容
CZ3-1中,字符数组a的容量是6个字W,其内容ؓhello。a的内容可以改变,如a[0]=
‘X’。指针p指向帔R字符?#8220;world”Q位于静态存储区Q内容ؓworldQ,帔R字符串的内容是不可以被修改的。从语法上看Q编译器q不觉得语句
p[0]= ‘X’有什么不妥,但是该语句企图修改常量字W串的内容而导致运行错误?br>
char a[] = “hello”; a[0] = ‘X’; cout << a << endl; char *p = “world”; // 注意p指向帔R字符?br>p[0] = ‘X’; // ~译器不能发现该错误 cout << p << endl; |
// 数组… char a[] = "hello"; char b[10]; strcpy(b, a); // 不能?b = a; if(strcmp(b, a) == 0) // 不能?if (b == a) … // 指针… int len = strlen(a); char *p = (char *)malloc(sizeof(char)*(len+1)); strcpy(p,a); // 不要?p = a; if(strcmp(p, a) == 0) // 不要?if (p == a) … |
char a[] = "hello world"; char *p = a; cout<< sizeof(a) << endl; // 12字节 cout<< sizeof(p) << endl; // 4字节 |
void Func(char a[100]) { cout<< sizeof(a) << endl; // 4字节而不?00字节 } |
CZ3.3QbQ?数组退化ؓ指针
4、指针参数是如何传递内存的Q?br>
如果函数的参数是一个指针,不要指望用该指针ȝ请动态内存。示?-4-1中,Test函数的语句GetMemory(str, 200)q没有str获得期望的内存,str依旧是NULLQؓ什么?
void GetMemory(char *p, int num) { p = (char *)malloc(sizeof(char) * num); } void Test(void) { char *str = NULL; GetMemory(str, 100); // str 仍然?NULL strcpy(str, "hello"); // q行错误 } |
void GetMemory2(char **p, int num) { *p = (char *)malloc(sizeof(char) * num); } void Test2(void) { char *str = NULL; GetMemory2(&str, 100); // 注意参数?&strQ而不是str strcpy(str, "hello"); cout<< str << endl; free(str); } |
char *GetMemory3(int num) { char *p = (char *)malloc(sizeof(char) * num); return p; } void Test3(void) { char *str = NULL; str = GetMemory3(100); strcpy(str, "hello"); cout<< str << endl; free(str); } |
char *GetString(void) { char p[] = "hello world"; return p; // ~译器将提出警告 } void Test4(void) { char *str = NULL; str = GetString(); // str 的内Ҏ垃圾 cout<< str << endl; } |
char *GetString2(void) { char *p = "hello world"; return p; } void Test5(void) { char *str = NULL; str = GetString2(); cout<< str << endl; } |
char *p = NULL; char *str = (char *) malloc(100); |
class A { public: void Func(void){ cout << “Func of class A” << endl; } }; void Test(void) { A *p; { A a; p = &a; // 注意 a 的生命期 } p->Func(); // p?#8220;野指?#8221; } |
函数Test在执行语句p->Func()Ӟ对象a已经消失Q而p是指向a的,所以p成?#8220;野指?#8221;。但奇怪的是我q行q个E序时居然没有出错,q可能与~译器有兟?/p>
6、有了malloc/freeZ么还要new/deleteQ?br>
malloc与free是C++/C语言的标准库函数Qnew/delete是C++的运符。它们都可用于申请动态内存和释放内存?br>
对于非内部数据类型的对象而言Q光用maloc/free无法满动态对象的要求。对象在创徏的同时要自动执行构造函敎ͼ对象在消亡之前要自动执行析构
函数。由于malloc/free是库函数而不是运符Q不在编译器控制权限之内Q不能够把执行构造函数和析构函数的Q务强加于malloc/free?br>
因此C++语言需要一个能完成动态内存分配和初始化工作的q算WnewQ以及一个能完成清理与释攑ֆ存工作的q算Wdelete。注?
new/delete不是库函数。我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,见示??br>
class Obj { public : Obj(void){ cout << “Initialization” << endl; } ~Obj(void){ cout << “Destroy” << endl; } void Initialize(void){ cout << “Initialization” << endl; } void Destroy(void){ cout << “Destroy” << endl; } }; void UseMallocFree(void) { Obj *a = (obj *)malloc(sizeof(obj)); // 甌动态内?br> a->Initialize(); // 初始?br> //… a->Destroy(); // 清除工作 free(a); // 释放内存 } void UseNewDelete(void) { Obj *a = new Obj; // 甌动态内存ƈ且初始化 //… delete a; // 清除q且释放内存 } |
void Func(void) { A *a = new A; if(a == NULL) { return; } … } |
void Func(void) { A *a = new A; if(a == NULL) { cout << “Memory Exhausted” << endl; exit(1); } … } |
void main(void) { float *p = NULL; while(TRUE) { p = new float[1000000]; cout << “eat memory” << endl; if(p==NULL) exit(1); } } |
CZ7试图耗尽操作pȝ的内?/p>
8、malloc/free 的用要?br>
函数malloc的原型如下:
void * malloc(size_t size); |
int *p = (int *) malloc(sizeof(int) * length); |
cout << sizeof(char) << endl; cout << sizeof(int) << endl; cout << sizeof(unsigned int) << endl; cout << sizeof(long) << endl; cout << sizeof(unsigned long) << endl; cout << sizeof(float) << endl; cout << sizeof(double) << endl; cout << sizeof(void *) << endl; |
void free( void * memblock ); |
int *p1 = (int *)malloc(sizeof(int) * length); int *p2 = new int[length]; |
class Obj { public : Obj(void); // 无参数的构造函?br> Obj(int x); // 带一个参数的构造函?br> … } void Test(void) { Obj *a = new Obj; Obj *b = new Obj(1); // 初gؓ1 … delete a; delete b; } |
Obj *objects = new Obj[100]; // 创徏100个动态对?/td> |
Obj *objects = new Obj[100](1);// 创徏100个动态对象的同时赋初? |
delete []objects; // 正确的用?br>delete objects; // 错误的用?/td> |