您的位置:首页 > 编程语言 > C语言/C++

《 高质量C++编程指南 》学习重点五

2011-05-06 10:29 295 查看

7.4

指针参数是如何传递内存的?

如果函数的参数是一个指针,不要指望用该指针去申请动态内存。
示例
7-4-1
中,
Test

数的语句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指向所申请的内存),但是p
丝毫未变。所以函数GetMemory
并不能输出任何东西。
事实上,每执行一次GetMemory
就会泄露一块内存,因为没有用free
释放内存。

如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,
见示例
7-4-2


void GetMemory2(char **p, int num)

{

*p = (char *)malloc(sizeof(char) * num);

}

void Test2(void)

{

char *str = NULL;

GetMemory2(&str, 100); //

注意参数是
&str
,而不是str

strcpy(str, "hello");

cout<< str << endl;

free(str);

}

示例
7-4-2
用指向指针的指针申请动态内存

由于
“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。
这种方法更加简单,见示例
7-4-3


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);

}

示例
7-4-3

用函数返回值来传递动态内存

用函数返回值来传递动态内存这种方法虽然好用,但是常常 有人把
return
语 句用错了。这里强调不要用

return

语句返回指向“栈内 存”的指针,因为该内存在函数结束时自动消亡,
见示例
7-4-4


char *GetString(void)

{

char p[] = "hello world";

return p; //

编译器将提出警告,p在“栈内存区”

}

void Test4(void)

{

char *str = NULL;

str = GetString(); // str

的内容是垃圾

cout<< str << endl;

}

示例
7-4-4

return
语句返回指向“栈内存”的指针

用调试器逐步跟踪Test4
,发现执行str = GetString
语 句后str
不再是NULL
指针,但是str
的内容不是

hello world

而是垃圾。

如果把
示例
7-4-4
改写成
示例
7-4-5
,会怎么样?

char *GetString2(void)

{

char *p = "hello world";

return p;

}

void Test5(void)

{

char *str = NULL;

str = GetString2();

cout<< str << endl;

}

示例
7-4-5

return
语句返回常量字符串

函数Test5
运行虽然不会出错,但 是函数GetString2
的设计概念却是错误的。
因为GetString2
内的“hello world
”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2
,它返回的始终是同一个“只读”的内存块。

小结:

若用指针参数去申请内存,有两种办法:

1.
用 “指向指针的指针”作为函数参数。
如示 例
7-4-2

2.用函数返回值来传递动态内存
。如示例
7-4-3

7.5 free


delete
把指针怎么啦?

别看
free

delete
的名字恶狠狠的(尤其是
delete
),它们只是把指针 所指的内存给释放掉,但并没有把指针本身干掉。

用调试器跟踪示例
7-5
,发现指针

p

free
以后其地址仍然不变(非
NULL
),只是该地址对应的内存是垃圾,
p
成了“野指针”。如果此时不把
p
设置为
NULL
,会让人误以为
p
是个合法的指针。

如果程序比较长,我们有时记不住
p
所指的内存是否已经被释放,在继续使用
p
之前,通常会用语句
if (p != NULL)
进行防错处 理。很遗憾,此时
if
语句起不到防错作用,因为即便

p
不是
NULL
指针,它也不指向合法的内存块。

char *p = (char *) malloc(100);

strcpy(p,


hello

);

free(p); // p

所指的内存被释放,但是
p
所指的地址仍然不变



if(p != NULL) //

没有起到防错作用

{

strcpy(p,


world

); //

出错

}

示例
7-5 p
成为野指针

7.6

动态内存会被自动释放吗?

函数体内的局部变量在函数结束时 自动消亡。很多人误以为示例
7-6
是正确的。理由是
p
是局部的指针变量,它消亡的时候会让它所指的动态内存一起完蛋。这是错觉!

void Func(void)

{

char *p = (char *) malloc(100); //

动态内存会自动释放吗?

}

示例
7-6

试图让动态内存自动释放

我们发现指针有一些“似是而非”的特征:


1)指 针消亡了,并不表示它所指的内存会被自动释放。

(2
)内存被释放了,并不表示指针会消亡或者成了NULL指针。

这表明释放内存并不是一件可以草 率对待的事。也许有人不服气,一定要找出可以草率行事的理由:

如果程序终止了运行,一切指针都会消亡,动态内存会被操作系统回收。既然如此,在程序 临终前,就可以不必释放内存、不必将指针设置为NULL
了。终于可以偷懒而不会发生错误了吧?

想得美。如果别人把那段程序取出来用到其它地方怎么办?

小结:

函数体内的局 部变量在函数结束时自动消亡,因局部变量和参数都在栈内存。但如示例
7-6,虽然p是局部的指针变量,但其申请的是堆内存,并不会随着函数执行结束而消亡,导致内存泄漏。

7.7

杜绝“野指针”

“野指针”不是
NULL
指针,是指向“垃圾”内存的指针。人 们一般不会错用
NULL
指针,因为用
if
语句很容易判断。但是“野指针”是很危险的,
if
语句对它不起作用。

“野指针”的成因主要有两种:


1
)指针变量没有被初始化。
任 何指针变量刚被创建时不会自动成为

NULL
指针,它的缺省值是随机的,它会乱 指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为
NULL

,要么让它指向合法 的内存。
例如

char *p = NULL;

char *str = (char *) malloc(100);


2
)指针

p

free
或者
delete
之后,没有置为
NULL
,让人误以为
p

是个合法的指针。
参见
7.5
节。


3
)指针操作超越了变量的作用范围。这种情况让人防不胜防,
示例程序如下:

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
是“野指针”

}

函数
Test
在执行语句
p->Func()

,对象
a
已经消失,而
p
是指向
a
的,所以
p
就成了

“野指针”。
但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: