C++编程指南学习(七),第1张

C++编程指南学习(七),第2张

第七章内存管理
欢迎来到内存雷区。伟大的比尔·盖茨曾经犯过一个错误:
640k应该够每个人用了
—比尔·盖茨1981
程序员经常写内存管理程序,他们总是很担心。如果你不想触雷,最好的解决办法就是找到所有潜伏的地雷并清除它们。你不能躲着他们。这一章的内容比一般教材要深入得多,所以读者要仔细阅读,才能真正理解内存管理。
7.1内存分配方法
内存分配方法有三种:
(1)从静态存储区。内存在程序编译时就已经分配好了,在程序的整个运行期内存都是存在的。比如全局变量,静态变量。
(2)在堆栈上创建。函数执行时,可以在栈上创建函数中局部变量的存储单元,这些存储单元在函数执行结束时自动释放。堆栈内存分配内置在处理器的指令集中,效率非常高,但分配的内存容量有限。
(3)从堆中分配,也叫动态内存分配。程序运行时,用malloc或new申请任意数量的内存,程序员负责什么时候用free或delete释放内存。动态记忆的寿命是由我们决定的。它使用起来非常灵活,但也有最多的问题。
7.2常见的内存错误及其对策
出现内存错误是非常麻烦的。编译器不能自动发现这些错误,但它通常可以在程序运行时捕获它们。但这些错误大多没有明显的症状,时有出现,时有消失,这就增加了改正的难度。有时候用户让你很生气,但是程序没有任何问题。你一走,错误又来了。
常见内存错误及其对策如下:
u内存分配失败,但已被使用。
新手程序员经常犯这个错误,是因为没有意识到内存分配会不成功。常见的解决方法是在使用内存之前检查指针是否为空。如果指针p是函数的参数,使用assert(p!=NULL)进行检查。如果malloc或new用于申请内存,则为if(p==NULL)或if(p!=NULL)进行防错处理。
u内存分配成功,但在初始化之前引用它。
造成这个错误的原因主要有两个:一是没有初始化的概念;二是误以为内存默认初始值都是零,导致引用初始值(比如数组)的错误。
内存的默认初始值是多少,并没有统一的标准,虽然有时候是零,我们宁愿相信它是可以信任的。所以不管你怎么创建数组,别忘了赋初值,连零值都不能省略。不用麻烦了。
u内存分配成功并已初始化,但操作越过了内存边界。
例如,下标“大于1”或“小于1”在使用数组时经常出现。尤其是在for循环语句中,循环次数容易弄错,导致数组运算越界。
u忘记释放内存,导致内存泄漏。
出现此错误的函数每次被调用时都会丢失一段内存。一开始,系统有足够的内存,所以你看不到错误。最后程序突然死机,系统显示提示:内存耗尽。
动态内存的申请和释放必须成对,malloc和free在程序中的使用次数必须相同,否则必然出错(new/delete也是如此)。
u释放了内存,但继续使用它。
有三种情况:
(1)程序中的对象调用关系太复杂,确实很难知道一个对象是否释放了内存。这时候就要重新设计数据结构,从根本上解决对象管理混乱的局面。
(2)函数的返回语句错误。注意不要返回指向堆栈内存的指针或引用,因为内存会在函数体结束时自动销毁。
(3)使用free或delete释放内存后,指针不设置为NULL。导致“野指针”。
l[规则7-2-1]用malloc或new申请内存后,要立即检查指针值是否为空。防止使用指针值为空的内存。
l [Rule 7-2-2]不要忘记给数组和动态内存赋值初始值。防止未初始化的内存被用作正确的值。
l[规则7-2-3]避免数组下标或指针越界,特别要提防“大于1”或“小于1”的操作。
l [Rule 7-2-4]动态内存的申请和释放必须成对,防止内存泄漏。
l[规则7-2-5]用free或delete释放内存后,立即将指针设置为NULL,防止出现“野指针”。
7.3指针和数组的比较
在C++/C程序中,指针和数组在很多地方可以互相替换,给人一种两者等价的错觉。
数组要么在静态存储区域(如全局数组)中创建,要么在堆栈上创建。数组的名字对应(而不是指向)一块内存,它的地址和容量在生存期内保持不变,只有数组的内容可以改变。
指针可以在任何时候指向任何类型的内存块。它的特点是“易变”,所以我们经常用指针来操作动态内存。指针远比数组灵活,但也更危险。
我们以字符串为例,比较一下指针和数组的特点。
7.3.1修改内容
在例7-3-1中,字符数组A的容量为6个字符,其内容为hello[2]。a的内容可以改变,比如a [0] =' x '。指针p指向常量字符串“world”(位于静态存储区,内容为world[2]),常量字符串的内容不可修改。从语法上来说,编译器并不认为语句p [0] =' x '有什么问题,但是这条语句试图修改常量字符串的内容,导致运行错误。
char a[]= " hello ";
a[0]= ' X ';
cout \u a \u endl;
char * p = " world ";//注意p指向常量字符串p[0]= ' x ';//编译器找不到此错误
cout < < p < < endl;

示例7-3-1修改数组和指针的内容
7.3.2复制和比较内容
不能直接复制和比较数组名。在例7-3-2中,如果要将数组A的内容复制到数组B中,就不能使用语句b = a,否则会得到编译错误。应该用标准库函数strcpy复制它。同样,不能用if(b==a)来判断B和A的内容是否相同,而要用标准库函数strcmp来比较。
语句p = a并没有把A的内容复制到指针P上,而是把A的地址赋给P,要复制A的内容,可以先用库函数malloc为P申请一个strlen(a)+1个字符的内存,然后用strcpy复制字符串。同样,语句if(p==a)比较的不是内容而是地址,应该和库函数strcmp进行比较。
//array…
char a[]= " hello ";
char bvoid test 2(void)
{
char * str = NULL;
GetMemory2(&str,100);//注意参数是&str,不是str
strcpy(str," hello ");
cout \u str \u endl;
free(str);
};
strcpy(b,a);//不能用b = a;
if(strcmp(b,a) == 0) //不能用if (b == a)

//pointer…
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)

示例7-3-2复制并比较数组和指针的内容
7.3.3计算内存容量
数组的容量(字节数)可以通过使用运算符sizeof来计算。在例7-3-3(a)中,sizeof(a)的值是12(注意不要忘记' [5] ')。p指向A,但是sizeof(p)的值是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*)而不是p所指向的内存容量,C++/C语言没有办法知道指针所指向的内存容量,除非在申请内存的时候记住。
注意,当数组作为函数的参数传递时,数组会自动退化为同类型的指针。在例7-3-3(b)中,不管数组A的容量是多少,sizeof(a)总是等于sizeof(char *)。
char a[]= " hello world ";
char * p = a;
cout〈sizeof(a)〉endl;// 12字节
cout < < sizeof(p)< < endl;// 4个字节

例7-3-3(a)计算数组和指针的内存容量
void func(char a[100])
{
cout < < sizeof(a)< < endl;// 4个字节而不是100个字节
}

例7-3-3(b)数组退化为指针
7.4指针参数如何通过内存?
如果函数的参数是指针,就不要指望用指针申请动态内存。在例7-4-1中,测试函数的语句GetMemory(str,200)并没有使str得到期望的内存,str仍然为NULL。为什么?
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 ");//运行错误
}

例7-4-1尝试用指针参数申请动态内存
问题出在函数GetMemory上。编译器总是为函数的每个参数创建一个临时副本。指针参数p的副本是_p,编译器使_ p = p,如果函数中的程序修改了_ p的内容,参数p的内容也会相应修改。这就是指针可以用作输出参数的原因。在这个例子中,_p申请了新的内存,但是只改变了_p引用的内存地址,而p根本没有改变。所以GetMemory函数不能输出任何东西。其实每次执行GetMemory都会有一块内存被泄露,因为内存不是用free释放的。
如果必须使用指针参数来申请内存,那么应该使用“指针对指针”来代替,如例7-4-2所示。
void GetMemory2(char **p,int num)
{
* p =(char *)malloc(sizeof(char)* num);
}

[10]

例7-4-2用指针对指针
申请动态内存由于“指针对指针”的概念不太好理解,我们可以用函数返回值来传递动态内存。这种方法比较简单,如例7-4-3所示。
char * get memory 3(int num)
{
char * p =(char *)malloc(sizeof(char)* num);
return p;
}

void Test3(void)
{
char * str = NULL;
str = get memory 3(100);
strcpy(str," hello ");
cout \u str \u endl;
free(str);
}

例7-4-3利用函数返回值转移动态内存
这种方法虽然简单易用,但人们经常错误地使用return语句。这里强调不要用return语句返回指向“堆栈内存”的指针,因为这个内存会在函数结束时自动消亡,如例7-4-4所示。char * GetString(void)
{
char p[]= " hello world ";
return p;//编译器会发出警告
}

void test 4(void)
{
char * str = NULL;

位律师回复
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » C++编程指南学习(七)

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情