在函数体内开辟动态内存时,函数形参选择指向指针的指针的原理解析
看到一道找错题,题目如下:
1 void GetMemory( char *p ) 2 { 3 p = (char *) malloc( 100 ); 4 } 5 void Test( void ) 6 { 7 char *str = NULL; 8 GetMemory( str ); 9 strcpy( str, "hello world" ); 10 printf( str ); 11 }
题目给出的答案是“
传入GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完
1 char *str = NULL; 2 GetMemory( str );
后的str仍然为NULL;”
这个答案用一句常识一笔带过,并没有做更深的讲解,我也没搜索到更满意的答案,就自己思考了一下,想到了一个较为形象的理解,可供参考。
首先,函数GetMemory的形参p是一个char *类型,它是实参str的一个副本,也就是说,当调用GetMemory函数的时候,创建p,与此同时,将str的“值”即str指向的内存首地址赋值给p,p和str指向同一块内存区域,然后才是函数体的处理,这个处理的过程有一定的迷惑性,也是理解这个题目的关键:函数体申请了一块新的动态内存,(注意:这块新申请的内存与原来str和p指向的那块内存无关)然后,将申请的这块内存的首地址赋值给形参p,这个时候有意思啦,str指向的内存还是老样子,但形参p已经改变了指向,也就是说p的指向与str的指向已经不同,这其实是导致Test函数中错误的根源。
如果GetMemory的形参是char **p的形式,p指向的是一个(指向char型变量的)指针,传入实参需要&str才能匹配,调用GetMemory的流程是:创建指针p,然后将str所在内存的首地址赋值给它,此时,对(*p)的操作就是对str的操作,就可以避免题目中的错误。
2014-05-22补充
从下面的函数可以更好的理解相关内容:
char *pG=NULL; //全局变量 void GetMemory(char *p) { p=(char *)malloc(100); pG=p; }
当Test函数中调用GetMemory时,单步调试可以发现,GetMemory函数结束后,p被释放。从本质上讲,p仅仅是一个局部与函数的变量,而且是自动变量,所有局部变量当作用域结束的时候都会被释放。同时,这个过程中从pG的值可知,使用malloc开辟的内存并没有随着函数GetMemory的结束而释放,这正是malloc和free需要成对出现的原因:使用malloc开辟的内存必须手动释放,,而且可以在任何“可见”的位置释放,不一定局限于开辟它的函数内。
函数传参有三种:值传递、传地址、引用型形参,但值传递和传地址有一个共同本质:仅仅形参变量本身而言,都是值传递的副本形式,只是说当传地址的时候,形参开辟了一个实参的副本,指向了原本实参指向的内存。
另外,【标注①】只能通过调用strcpy函数来实现将一个字符串赋给一个字符数组,而不能直接用赋值语句将一个字符串常量或字符数组直接赋给一个字符数组。如下是两个例子:
char str1[10],str2[]="China"; str1="China"; //错误:不能将一个字符串常量赋给字符数组 str1=str2; //错误:不能将一个字符数组的内容赋给另一个字符数组 strcpu(str1,"China"); //正确:
char * str=NULL; str=(char *)malloc(10); //str="China"; //此句错误 strcpy(str,"China"); //正确 free(str);
第二个例子中使用字符串常量直接赋值给字符串指针的形式也可以输出正确结果,但是,当运行到free函数的时候,程序报错,提示“堆被破坏”,只有使用srtcpy才能正确运行。
第一个例子是《C++程序设计》(谭浩强版)第154页的的原始内容,也正是刚刚复习过这章的内容,所以当运行第二个例子的时候,很快发现了问题出在哪里
2014-06-26
另外在《C++程序设计》(谭浩强版)第173页有下面一句话:
调用函数时,不会改变实参指针变量的值,但可以改变实参指针变量所指向变量的值。
所以,如果想在调用函数时改变实参指针变量的值,用指向指针的指针即可达到目的。
有很多人说谭版的《C++程序设计》很简单,看过以后没学到什么东西,这点我一直以来都不认同,我甚至觉得直至今日,它仍是我看过的C++相关书籍中最优秀的书籍,通俗易懂又细致缜密,有时候,读书不一定挑最难的读,正像《西游降魔篇》中一样,或许《真正的大日如来真经》正是那本《儿歌三百首》。