按值传递还是按引用传递?

4
在下面的代码中,我向函数传递指向*example[10];的指针还是整个数组?
#include <stdio.h>

void function (int **)

int main ()
{
    int *example[10];
    function(example);
    return 0;
}

void function (int *example[10])
{
    /* ... */
    return;
}

以下代码有同样的问题:
#include <stdio.h>

struct example
{
    int ex;
};

void function (struct example *)

int main ()
{
    struct example *e;
    function(e);
    return 0;
}

void function (struct example *e)
{
    /* ... */
    return;
}

2
在 C 语言中没有传递引用的方式。你可以通过传递指向指针的指针来模拟传递引用。 - Tobias Wärre
2
是的,通过引用传递的“指针到指针”的模拟。例如,最好通过这种“模拟”传递一个结构体,而不是整个结构体,对吗? - ᴜsᴇʀ
1
当使用简单指针时,不需要额外的指针来传递结构体。请注意,对于传递非指针的任何更改,将仅在该函数中局部生效,而如果通过指针传递,则结果将在该函数返回后仍可见。这里有一罐蚯蚓,因此如果您想要尝试,请进行简单的实验,以便轻松找出错误所在。 - Tobias Wärre
1
@TobiasWärre。我之所以提出这些问题,是因为我只是在进行实验;同时,我还没弄清楚为什么会有“无需额外指针即可传递结构体”的说法!我原本以为通过额外指针可以在函数中修改结构体(并且在 main() 中也会发生变化),而没有额外指针时只能在本地修改! - ᴜsᴇʀ
1
如果您只有一个指向结构体的指针,仍然可以对在该函数之外可见的结构体进行更改。但是,如果您更改指向该结构体的指针,则更改是局部的。这就是为什么要模拟按引用传递,您需要传递一个指向要修改的指针的指针。(因此,您可以修改指针所指向的对象,即指针)。 - Tobias Wärre
2个回答

5

C语言中所有参数都是按值传递的,包括指针。如果传递数组,数组会“衰变”为指向其初始元素的指针。

您的第一个函数传递了一个指向十个未初始化指向int的指针块的指针。这可能很有用,因为function(int**)可以将数组内的指针更改为有效的指针。例如,以下操作是被允许的:

void function (int *example[10])
{
    for (int i = 0 ; i != 10 ; i++) {
        // Allocate a "triangular" array
        example[i] = malloc((i+1)*sizeof(int));
    }
}

当然,现在调用程序需要负责所有已分配的内存。

您的第二个函数传递了一个未初始化的指针。这完全没有用,因为function(struct example *e)无法合法地分配或引用此指针。

这是不合法的:

void function (struct example *e)
{
    e->ex = 123; // UNDEFINED BEHAVIOR! e is uninitialized
}

这不会对调用者中的e值产生影响:
void function (struct example *e)
{
    e = malloc(sizeof(struct example)); // Legal, but useless to the caller
}

2
但如果它是 function (&e);void function (struct example **e),那么 *e = malloc(sizeof(struct example)); 是合法和有用的,对吗? - ᴜsᴇʀ
1
如果在 void function (struct example *e) 中使用 e = malloc(sizeof(struct example)); 是“对调用者无用的”,那么是否有需要使用它的情况呢? - ᴜsᴇʀ
2
@user 将一个未初始化的指针传递给函数与声明一个未初始化的相同类型的局部变量是一样的。实际上,局部变量和参数之间唯一的区别在于后者由调用者进行初始化,而前者在函数内部进行初始化。调用 e = malloc(sizeof(struct example)); 可以让你像使用普通的局部变量一样使用 e,而不会导致崩溃。 - Sergey Kalinichenko
2
谢谢,我明白了。如果在 main() 函数中声明 struct example *e = NULL;,那么这是否被认为是已初始化的呢? - ᴜsᴇʀ
1
@user 是的,将指针设置为 NULL 将会对它进行初始化。这样做并不会使它更有用,因为您的 function(struct example *) 既不能对其进行解引用也不能重新分配它,但至少可以测试它是否为 NULL,并合法地验证它是否未指向有效的内存块。 - Sergey Kalinichenko

5
在C语言中,只有按值调用,因此所有值都是通过值传递给函数的(正式说法)。但是,在C语言中,数组与其他数据类型处理方式略有不同。因此,在第一个示例中,数组example被转换为指向其第一个元素的指针,然后将该指针(按值)传递给函数。在第二个示例中,e是一个指向结构体的指针(但它从未被设置为指向任何地方),然后将该指针(按值)传递给函数。在这两种情况下,参数传递都是按值传递的,但是当您传递一个指向变量的指针时,被调用的函数可以更改原始变量。

@haccks:你是怎么想到这更精确的?C标准在C 2011 6.3.2.1 3中使用“converted”,而不是“decayed”。 - Eric Postpischil
@EricPostpischil;由K&R C编写。忘记了标准引用。对此很抱歉。 - haccks
1
在这两种情况下,参数传递都是按值传递的,但是当你传递一个指向变量的指针时,被调用的函数可以更改原始变量。但是在第二段代码中,我无法更改原始变量!为了进行更改,我必须这样做:function(&e);void function(struct example **e),对吗? - ᴜsᴇʀ
1
@user "但是在第二段代码中,我无法更改原始变量!" 在第二种情况下,您没有一个原始变量,因为您正在传递指向指针类型函数参数的指针,并且您传递的指针未初始化。如果您执行struct example se; struct example *e = &se; function(e),则将能够更改原始变量,这种情况下原始变量将是se而不是e - Sergey Kalinichenko
@Thomas Padron-McCarthy。我不理解。请看这里的例子:https://dev59.com/S3vaa4cB1Zd3GeqPADPQ#21070007 - ᴜsᴇʀ
@user 是的,这是同样的情况:如果你想修改一个变量,你需要传递一个指向它的指针。如果你的变量是一个指针,那么传递一个指向指针的指针;如果你的变量是一个指向指针的指针,那么传递一个指向指针的指针的指针,以此类推。 - Sergey Kalinichenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接