传递结构体指针 vs 传递结构体

7

最近我写了很多传递结构体到函数中的程序,以避免使用全局变量。然而,我想知道传递结构体本身还是它的指针更有效率。听起来应该是指针更有效率,因为在我的64位GNU/Linux系统上,指针占用8个字节,而一个充满指针的结构体显然远远超过这个大小。

然而,如果我有这个结构体:

struct Point {
    int x;
    int y;
}

一个结构体的大小通常为 8 字节,和指针一样大。在函数传参时,是直接传整个结构体更好呢,还是只传指针?我对 C 语言内存分配比较熟练,所以使用 malloc 等函数初始化指针也不会有问题。

另一个需要考虑的因素是,如果结构体很大,直接传递结构体可能会占用大量的栈空间。而仅仅传递指针则会占用一些内存,可以很容易地使用 free 进行释放。


1
按引用传递通常更有效率,这取决于结构体的大小。但对于像您的“点结构”这样的小结构体,没有多少差别。 - Ian Abbott
你还应该考虑可能的编译器优化。如果你传递结构体的引用,编译器可以使用处理器寄存器之一来保存它并避免访问内存,这样速度会更快。在简单的应用程序中,你不会看到太大的差异,但在复杂的情况下,它可以提供更好的性能。 - Ari0nhh
我认为“优化”这个论点并不成立。在运行Linux的64位Intel上,ABI 要求第一个8字节参数必须通过%rdi寄存器传递。在这种特定情况下,指针和结构体都将通过一个单一寄存器传递。因此,传递值更快,因为传递指针将生成2个不必要的内存访问。 - ArjunShankar
在这种特定情况下(64位Intel CPU运行Linux时的8字节结构体与8字节结构体的指针),传递指针实际上更慢。@Ari0nhh - ArjunShankar
1
转念一想:在32位系统上,调用者必须将这两个值放置在堆栈上,这也是真的。这将比仅放置指针多进行一次内存访问。因此,两者都会导致相同数量的内存访问,但传递值将在堆栈上占用4个额外字节。所以考虑到这一点,在32位系统上,传递指针更胜一筹。但不要忘记缓存未命中。 - ArjunShankar
显示剩余3条评论
1个回答

10

[这个问题和它的回答已经比较全面地讨论了传递结构体与结构体指针的优缺点。本回答旨在讨论本问题中提到的特定情况,即8字节结构体与8字节指针以及ABI在寄存器中传递参数的情况。]

在运行Linux的64位Intel CPU上,ABI要求通过寄存器传递8字节参数,直到没有参数为止。例如,第一个参数通过%rdi寄存器传递。这并不是优化的问题,而是ABI的要求。

在这种特殊情况下(8字节结构体与8字节指针),指针和结构体都将通过一个单一的寄存器传递。也就是说,两种情况都不使用栈。实际上,如果您有一个足够简单的函数,例如:

int
add (struct Point p)
{
  return p.x + p.y;
}

如果使用gcc -O1编译,函数甚至不会有一个栈帧。

在生成的代码中可以看到这一点(x86_64 Linux gcc 5.1,使用-O1):

# Passing the struct:
movq    %rdi, %rax
sarq    $32, %rax
addl    %edi, %eax
ret

# Passing a pointer to the struct:
# [each (%rdi) is a memory access]
movl    4(%rdi), %eax
addl    (%rdi), %eax
ret

但是,如您所见,指针版本会访问内存两次。因此,传递值更快。传递指针将生成内存访问以获取结构体的成员。还有一种额外的风险,即结构体可能在未被CPU缓存的内存块上,并且访问将导致缓存未命中。这不应该发生,因为通常,调用者只需访问相同的结构体,因此它在缓存中。

在32位Linux上,int仍然是4个字节,但指针变小了(从8减少到4)。由于参数在堆栈上传递,这意味着传递指针可以在堆栈上节省4个字节(与8个字节的结构体相比,vs 4个字节的指针)。但我仍然喜欢按值传递,因为它可以改善空间局部性。


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