值传递, 指针传递?
这几天在学习C过程中,在使用指针作为函数参数传递的时候出现了问题,根本不知道从何得解:源代码如下:
createNode(BinNode *tree,char *p)
{
tree = (BinNode *) malloc(sizeof(BinNode));
tree->data = *p;
}
该代码段的意图是通过一个函数创建一个二叉树的节点,然而在,调用该函数后,试图访问该节点结构体的成员时候,却发生了内存访问错误,到底问题出在哪儿呢?
一直不明白指针作为函数参数传值的机制,翻开林锐的《高质量C/C++编程指南》,找到了答案。
[如果函数的参数是一个指针,不要指望用该指针去申请动态内存]
原来问题出在C编译器原理上:编译器总是要为函数的每个参数制作临时副本,指针参数tree的副本是 _tree,编译器使 _tree = tree。如果函数体内的程序修改了_tree的内容,就导致参数tree的内容作相应的修改。这就是指针可以用作输出参数的原因。
即上面的函数代码经过编译后成为:
createNode(BinNode *tree,char *p)
{
BinNode *_tree;
_tree = tree;
_tree = (BinNode *) malloc(sizeof(BinNode));
_tree->data = *p;
}
如果没有
_tree = (BinNode *) malloc(sizeof(BinNode));
这个语句,在函数体内修改了_tree的内容,将会导致参数tree的内容作相应的修改,因为它们指向相同的内存地址。而
_tree = (BinNode *) malloc(sizeof(BinNode));
这个句,系统重新分配内存给_tree指针,_tree指针指向了系统分配的新地址,函数体内修改的只是_tree的内容,对原tree所指的地址的内容没有任何影响。因此,函数的参数是一个指针时,不要在函数体内部改变指针所指的地址,那样毫无作用,需要修改的只能是指针所指向的内容。即应当把指针当作常量。
如果非要使用函数指针来申请内存空间,那么需要使用指向指针的指针
createNode(BinNode **tree,char *p)
{
*tree = (BinNode *) malloc(sizeof(BinNode));
}
上面的是林锐的说法,目前来说不知道怎么去理解,不过可以有另外的方案,通过函数返回值传递动态内存:
BinNode *createNode()
{
BinNode *tree;
tree = (BinNode *) malloc(sizeof(BinNode));
return tree;
}用函数返回值来传递动态内存,可行
这个倒还说得过去,因为函数返回的是一个地址的值,该地址就是申请的内存块首地址。但是,这个容易和另外的一个忠告相混绕
[不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡]
这里区分一下静态内存,栈内存和动态分配的内存(堆内存)的区别:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
因此,试图返回一个栈上分配的内存将会引发未知错误
char *GetString(void)
{
char p[] = "hello world";
return p; // 编译器将提出警告
}失败,不可行
p是在栈上分配的内存,函数结束后将会自动释放,p指向的内存区域内容不是"hello world",而是未知的内容。
如果是返回静态存储的内存呢:
char *GetString(void)
{
char *p = "hello world";
return p;
}
这里“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString,它返回的始终是同一个“只读”的内存块。
[参考:林锐《高质量C/C++编程指南》]
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Twent/archive/2005/10/10/498955.aspx
补充:
void pointer(int *p)
{
int a = 11;
printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
*p =11;
printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
p = &a;
printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
}
int main()
{
int b =22;
int *p = &b;
printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
pointer(p);
printf("\nthe p is %p , addr is %d, *p is %d",p , &p, *p);
}
the p is 0xbfd46498 , addr is -1076599652, *p is 22
the p is 0xbfd46498 , addr is -1076599680, *p is 22
the p is 0xbfd46498 , addr is -1076599680, *p is 11
the p is 0xbfd4646c , addr is -1076599680, *p is 11
the p is 0xbfd46498 , addr is -1076599652, *p is 11
1、例子中,指针p的拷贝传入了方法中(其地址变了,说明是另一变量;值和指向的内存块数据没变)
2、将p的拷贝视作p1,p1改变了其所指向的内存块的值为11
3、p1的值改变为a的地址,即p1指向a,此时p1与p分别指向不同的内存块了,不会互相影响
4、方法结束,p地址和值没变(改变的仅仅是p的拷贝p1),但是p所指向的内存块数据被p1所改变了,故*p为11
总结:传入的指针仅仅是一个拷贝,方法不会改变原指针的地址、值,但是可能会改变原指针所指向内存块的数据。
再补充:void main()
{
char *p=NULL;
myMalloc(p); //这里的p实际还是NULL,p的值没有改变,为什么?
if(p) free(p);
}
void myMalloc(char *s) //我想在函数中分配内存,再返回
{
s=(char *) malloc(100);
}
myMalloc(p)的执行过程:
分配一个临时变量char *s,s的值等于p,也就是NULL,但是s占用的是与p不同的内存空间。此后函数的执行与p一点关系都没有了!只是用p的值来初始化s。
然后s=(char *) malloc(100),把s的值赋成malloc的地址,对p的值没有任何影响。p的值还是NULL。
注意指针变量只是一个特殊的变量,实际上它存的是整数值,但是它是内存中的某个地址。通过它可以访问这个地址。
程序2:void myMalloc(char **s)
{
*s=(char *) malloc(100);
}
void main()
{
char *p=NULL;
myMalloc(&p); //这里的p可以得到正确的值了
if(p) free(p);
}
程序2是正确的,为什么呢?看一个执行过程就知道了:
myMalloc(&p);将p的地址传入函数,假设存储p变量的地址是0x5555,则0x5555这个地址存的是指针变量p的值,也就是Ox5555指向p。
调用的时候同样分配一个临时变量char **s,此时s 的值是&p的值也就是0x5555,但是s所占的空间是另外的空间,只不过它所指向的值是一个地址:Ox5555。
*s=(char *) malloc(100);这一句话的意思是将s所指向的值,也就是0x5555这个位置上的变量的值赋为(char *) malloc(100),而0x5555这个位置上存的是恰好是指针变量p,这样p的值就变成了(char *) malloc(100)的值。即p的值是新分配的这块内存的起始地址。
这个问题理解起来有点绕,关键是理解变量作函数形参调用的时候都是要分配一个副本,不管是传值还是传址。传入后就和形参没有关系了,它不会改变形参的值。myMalloc(p)不会改变p的值,p的值当然是 NULL,它只能改变p所指向的内存地址的值。但是myMalloc(&p)为什么就可以了,它不会改变(&p)的值也不可能改变,但是它可以改变(&p)所指向内存地址的值,即p的值