4.8-引用传递的骗局

在以前的 C 教科书上,一直教导我们,C 语言函数有两种传递参数的方式:值传递和引用传递。但是,今天在看 c-faq 的 4.11 的时候,才了解到真相。其实严格意义上来讲,C 语言只有值传递,但是编译器可以模拟引用传递,但本质上,还是值传递。
举个例子:

1
2
3
4
void f(int *p);

int i;
f(&i);

这是一个很正常的指针传递,我们一般把这个称为引用传递。对于整数 i 来说,确实是这样的,但是对于 int 指针来说,f 函数调用,只是把 i 的地址复制一份给 p,这个行为其实就是传值。

可能这个例子不是很明显,那么引用一下 c-faq 上的一个例子:

1
2
3
4
5
6
7
8
void f(int *p)
{
static int dummy=5;
p=&dummy;
}

int *i;
f(i);

指针 i在 f 函数调用后,是否改变了?答案是否,因为 f(i) 是一个传值,只是复制了一份 i 的值而已,尽管在 f 函数内部改变了,但是只是改变了 i 的复制版。这其实是 C 语言初学者,经常犯的错误,把传值理解为传地址。但是为什么还是有很多人在这个代码上,犯糊涂了呢?因为我们默认把传指针直接当作引用传递的替身了。其实,我们为了传递一个指针的引用,应该用指针的指针:

1
2
3
4
5
6
7
8
void f(int **p)
{
static int dummy=5;
*p=&dummy;
}

int *i;
f(&i);

这样,对于指针 *p 来说,f 函数是传值,但是对于指针 p 来说,其实是传地址,跟上面是一个道理。

并且这也可以用来解释:为什么 free 掉一个指针所指向的内存后,指针的值没有发生改变?如果每次 free 内存后,把相应的指针重置为 0,这样不是更安全么?
为什么 C 中标准函数 free 没有这样来设计?由于 C 语言的天生的传值特性,如果你需要修改这个指针本身的值,那么意味着 free 的形参必须为指针的指针才行。