本文是自己对 C 语言参数传递的理解,实际上 C 语言只有值传递一种参数传递方式。

⚠️ 注意:本文写于 2021 年,内容较为基础。

回顾

在初学 C 语言时,从教材或者课堂上会学到 C 语言有两种参数传递方式,一种是值传递,另一种是地址传递。

  • 值传递只向函数中传递实参的值,形参是实参的拷贝,在函数中修改形参的值,并不会改变函数外实参的值。
  • 通常所说的“地址传递”是把对象的地址作为一个指针值传入函数。指针形参本身仍是副本;当它确实指向调用方的对象时,通过解引用可以修改那个对象。

例如要想交换 a, b 两个变量的值,应该写为

void swap(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

调用时需要写成 swap(&a, &b)。函数收到的是两个地址值的副本,但通过 *a、*b 访问的仍是调用方的两个对象,所以对象中的值会被调换。

问题发现

当我在写编译原理实验时,我想要通过递归遍历语法树,生成中间代码的链表,并每次插入在链表头部,这就需要在函数间传递一个指向链表头的指针。但我发现我并不能在调用函数后获取到处理后的链表头,好像并没有进入递归函数一样。

问题分析

指针在 C 语言中也是一个对象,它保存的是地址。把指针作为实参传入函数时,形参得到的是该地址值的副本。函数可以通过这个副本修改所指对象,却不能直接改写调用方那个指针对象本身。

因此 C 语言的参数传递机制只有值传递。“地址传递”是常见的教学简称,并不是第二种语言级传参机制;这里按值传递的内容恰好是一个地址。

那么如果想改变指针自身的值应该如何传参呢?

其实前面已经给出了答案,既然指针也是变量,那么要想改变函数外变量的值,只需要声明一个指向它的指针,然后向函数中传递这个指针的拷贝就可以了。所以要改变指针的值,需要传递指针的指针,即二级指针。

例如可以声明

void analyse(InterCode **node);

在使用时按如下方式调用:

InterCode* head = NULL;
analyse(&head);

如果真实函数还需要其他参数,可以把它们放在 node 之前。原先用来表示“其他参数”的 void analyse(..., InterCode **node) 不能作为 C 代码:省略号表示可变参数时只能出现在参数列表末尾。

函数在接收到 InterCode ** 后,可以通过 *node = new_head 改变调用方 head 保存的地址。这里仍然是值传递,只是传递的值恰好是 head 自身的地址。

这与 ISO C11 委员会草案 N1570 的 6.5.2.2一致:调用前先求值各实参,再把相应实参的值赋给各形参;修改形参不会反向修改实参,但可以通过传入的指针修改其所指对象。