1.常见的内存错误及对策
发生内存错误是件非常麻烦 的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把 你找来,程序却没有发生任何问题,你一走,错误又发作了。 常见的内存错误及其对策如下:
(1) 内存分配未成功,却使用了它。
编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在 函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。
(2)内存分配虽然成功,但是尚未初始化就引用它。
犯这种错误主要有两个 起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候 为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
(3)内存分配成功并且已经初始化,但操作越过了内存的边界。
例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循 环语句中,循环次数很容易搞错,导致数组操作越界。
(4)忘记了释放内存,造成内存泄露。
含有这种错误的函数每被 调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。动态内存的申请与 释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。
(5)释放了内存却继续使用它(所谓的野指针)。一般情况下,释放掉指针后将指针置为空。
2.指针与数组对比
C++/C程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容 可以改变。指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。
例子1:
字符数组a的 容量是6个字符,其内容为hello。a的内容可以改变,如a[0]= 'X’。指针p指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句 p[0]= 'X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。
- char a[] = “hello”;
- a[0] = 'X’;
- cout << a << endl;
- char *p = “world”; // 注意p指向常量字符串
- p[0] = 'X’; // 编译器不能发现该错误
- cout << p << endl; <span style="font-size: 14px; line-height: 22.383333206176758px; color: rgb(51, 51, 51); font-family: Verdana, Arial, Helvetica, sans-serif, 宋体; "> </span>
例子2:
用运算符sizeof可以计算出数组的容量(字节数)。注意当数组作为函数的参数进行传递时,该数组自动退化为 同类型的指针。不论数组a的容量是多少,sizeof(a)始终等于指针的容量
- char a[] = "hello world";
- char *p = a;
- cout<< sizeof(a) << endl; // 12字节(注意别忘了加上末尾的‘\0’)
- cout<< sizeof(p) << endl; // 4字节 示例3.3(a) 计算数组和指针的内存容量
- void Func(char a[100])
- {
- cout<< sizeof(a) << endl; // 4字节而不是100字节
- }
3.指针参数传递内存
用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。(下面的用法是对的)
- 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);
- } <span style="color: rgb(51, 51, 51); font-family: Verdana, Arial, Helvetica, sans-serif, 宋体; font-size: 14px; line-height: 22.383333206176758px; "> </span>
这里强调不要用 return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡。(下面就是错的)
- char *GetString(void)
- {
- char p[] = "hello world";
- return p; // 编译器将提出警告
- }
- void Test4(void)
- {
- char *str = NULL;
- str = GetString(); // str 的内容是垃圾
- cout<< str << endl;
- }
用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。
如果把示例改成下面这样
- char *GetString2(void)
- {
- char *p = "hello world";
- return p;
- }
- void Test5(void)
- {
- char *str = NULL;
- str = GetString2();
- cout<< str << endl;
- }
4.杜绝野指针
野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易 判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种:
- 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是“野指针”
- }