将结构作为参数按值传递给函数是否有效?

Is it efficient passing structs as parameters by value to a function?

提问人:Zaratruta 提问时间:8/26/2022 更新时间:8/26/2022 访问量:435

问:

据我所知,结构可以作为参数按值传递,或者我们也可以使用指针通过引用传递它。

按值传递结构是否有效?如果我们有很多字段的大结构体,这似乎效率不高,因为结构体数据被复制到函数的形式参数中,这涉及计算成本。

在这些情况下,通过引用传递结构似乎是更好的选择。这有意义吗?

另一个相关的问题是,编译器是否在这个意义上进行优化,将传递值的参数转换为引用。

C 指针 结构 参数传递

评论

2赞 Ted Lyngmo 8/26/2022
你最初关于它是否有意义的问题:是的,我认为它有意义。关于编译器优化:视情况而定。编译器可以自由地做任何它喜欢的事情,只要你的程序的编译版本的行为与你写的完全一样。
1赞 Ted Lyngmo 8/26/2022
@0___________为什么不呢?整个函数甚至可以内联,因此不会传递任何内容。
1赞 Ted Lyngmo 8/26/2022
@0___________我说“表现得好像它完全按照你写的做了” - 只要这是真的,它就可以做任何它喜欢的事情。
1赞 ShadowRanger 8/26/2022
@0___________:“不”与“不能”不同。你绝对不应该依赖它,但这是一个完全合法的优化。
2赞 Ted Lyngmo 8/26/2022
@0___________该类型信息与程序集中发生的情况没有任何关系。如果我有并调用它,您希望无条件地完成复制 - 但如果不需要这样做,因为编译器知道该函数不会更改传递的参数中的任何内容 - 它只是引用传递的参数并直接从中读取。什么规则阻止它这样做?void x(foo) {}

答:

-3赞 0___________ 8/26/2022 #1

它肯定不会有效率,但有时我们想处理局部自动变量。当您更改自动变量时,当函数返回时,它将停止存在,并且所有更改将仅对此局部变量进行。如果它是由指针传递的,则函数将修改原始对象,而不是本地对象。因此,编译器不会对其进行优化。

typedef struct
{
    double x[10000];
}big_struct;

void __attribute__((noinline)) foo(big_struct bs)
{
    for(size_t i =0; i < sizeof(bs.x) / sizeof(bs.x[0]); i++) printf("%f\n", bs.x[i]);
}


void bar(void)
{
    big_struct x;

    /* some code */

    foo(x);
}
.LC0:
        .string "%f\n"
foo:
        push    rbp
        push    rbx
        sub     rsp, 8
        lea     rbx, [rsp+32]
        lea     rbp, [rsp+80032]
.L2:
        movsd   xmm0, QWORD PTR [rbx]
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 1
        add     rbx, 8
        call    printf
        cmp     rbx, rbp
        jne     .L2
        add     rsp, 8
        pop     rbx
        pop     rbp
        ret
bar:
        sub     rsp, 160008
        mov     edx, 80000
        lea     rsi, [rsp+80000]
        mov     rdi, rsp
        call    memcpy
        call    foo
        add     rsp, 160008
        ret

评论

1赞 Ted Lyngmo 8/26/2022
@Zaratruta 不是我的反对票,但我认为这可能与问题下评论字段中正在进行的讨论有关。
0赞 0___________ 8/26/2022
@SupportUkraine答案是正确的。顺便说一句,它是双向的。
2赞 Eric Postpischil 8/26/2022 #2

如果结构很小,则按值传递它。现代调用约定允许在合适的平台上传递寄存器中的小结构。

如果结构很大,则可以定义例程以通过引用(使用指向限定类型的指针)接收结构。const

还应该使用 声明指针,如 中所示。否则,通过引用传递结构可能会影响优化。请考虑传递给同样具有参数 或 的例程。在例程中,如果代码更改了指向或指向的结构,则编译器通常无法知道哪个没有指向与结构指向相同的结构,或者哪个没有指向作为结构成员指向的 a。这可能会导致编译器多次重新加载数据,而实际上没有发生任何更改。using 告诉编译器调用方不会传递 a,因此使用 via 的数据也不会通过其他点在例程中使用。restrictconst struct foo * restrict pconst struct foo *pstruct foo *qfloat *fqfloatfqpffloatppprestrictpp

另一个相关的问题是,编译器是否在这个意义上进行优化,将传递值的参数转换为引用。

这并不常见。但是,通过引用返回结构是很常见的。调用约定通常是调用方为返回的结构提供要写入的位置,并传递指向该位置的指针,不可见地指向源代码。