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

来源:互联网 发布:js重置表单 编辑:程序博客网 时间:2024/06/05 08:06

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 freedelete把指针怎么啦?

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

用调试器跟踪示例7-5发现指针pfree以后其地址仍然不变(非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指针pfree或者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就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。