31.Free和delete对指针的操作
它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。 发现指针 p 被 free 以后其地址仍然不变(非 NULL) ,只是该地址对应的内存是垃圾,p 成了“野指针”。如果此时不把 p 设置为 NULL,会让人误以为 p 是个合法的指针
32.动态申请的内存是否会自动消亡
在一个子函数中,临时申请的变量会在子函数的结束时自动的消亡,但是用malloc申请的动态内存不会随着子函数的结束而消亡,但是会随着整个程序的结束而被Windows回收。但这也不能保证万无一失。指针消亡了,并不表示它所指的内存会被自动释放。内存被释放了,并不表示指针会消亡或者成了 NULL 指针。
33.野指针的问题
●指针变量没有被初始化。任何指针变量刚被创建时不会自动成为 NULL 指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存。
●指针 p 被 free 或者 delete 之后,没有置为 NULL,让人误以为 p 是个合法的指针。当继续使用该指针时就会产生意想不到的后果。
●指针操作超越了变量的作用范围。例如:
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 是“野指针”
}
34.变量的生存范围
一个临时的变量的生存时间就是离它最近的一个大括号结束的时候。因此大括号不可乱用。例如:
Int a = 1;
{
Int b = 0;
}
Int c = (a + b);//虽然编译可能通过但实际上b已经消失了,这是编译器问题。
35.Malloc和free的使用规则
●原型:void * malloc(size_t size);
malloc 返回值的类型是 void *,所以在调用 malloc 时要显式地进行类型转换,将
void * 转换成所需要的指针类型。
malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我
们通常记不住 int, float 等数据类型的变量的确切字节数。因此最好用sizeof()来计算要申请内存的大小。
●原型:void free( void * memblock );
为什么 free 函数不象 malloc 函数那样复杂呢?这是因为指针 p 的类型以及它所指
的内存的容量事先都是知道的,语句 free(p)能正确地释放内存。如果 p 是 NULL 指针,
那么 free 对 p 无论操作多少次都不会出问题。如果 p 不是 NULL 指针,那么 free 对 p
连续操作两次就会导致程序运行错误。
36.New和delete的使用规则
Type *varyName = new type(InnitValue);//动态申请一块内存,varyName指向它。并
//初始化
Type *pArrayName = new type[100];//创建动态数组,但不能同时初始化。
Delete varyName;
Delete []pArrayName;//释放动态数组不要忘记[].
37.两种动态申请内存的方法的区别
●malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。
●对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象
在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于
malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数
和析构函数的任务强加于 malloc/free。
所以我们不要企图用 malloc/free 来完成动态对象的内存管理,应该用 new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。
如果用 free 释放“new 创建的动态对象”,那么该对象因无法执行析构函数而可能
导致程序出错。如果用 delete 释放“malloc 申请的动态内存” ,理论上讲程序不会出错,
但是该程序的可读性很差。所以 new/delete 必须配对使用,malloc/free 也一样。
38.内存耗尽的处理
●判断指针是否为 NULL,如果是则马上用 return 语句终止本函数。
●判断指针是否为 NULL,如果是则马上用 exit(1)终止整个程序的运行。
●为 new 和 malloc 设置异常处理函数。
绝对不能姑息内存耗尽的应用程序,否则整个操作系统都会崩溃。
39.使用 const 提高函数的健壮性
●用const修饰函数的参数。如果输入参数采用“指针传递”,那么加 const修饰可以防止意外地改动该指针,起到保护作用。如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加 const 修饰。对于非内部数据类型的参数而言,象 void Func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生 A类型的临时对象用于复制参数 a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为 void Func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数 void Func(A &a) 存在一个缺点:“引用传递”有可能改变参数 a,这是我们不期望的。解决这个问题很容易,加 const
修饰即可,因此函数最终成为 void Func(const A &a)。
●用函数修饰函数的返回值。如果给以“指针传递”方式的函数返回值加 const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加 const修饰的同类型指针。如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加 const修饰没有任何价值。
40.指针的越界问题
Char *p = "hello"//不可越界,该内存在编译时分配。其附近都是不可写的区域,因此越界操作时会显示不可写。(静态内存分配)
Char *p//野指针,更不可越界读写,因为这会导致操作系统的崩溃。
Char *p = (char *)malloc(size)//可以越界操作,但是在free(p)时会崩溃。(在堆上分配内存)
Char p[Size] = "hello"//内存分配和Char *p = "hello"不再同时。这里的内存区域是在程序执行时分配的。可以越界读写,不会显示错误,但这也是危险的。(在栈上分配内存) 。、