【问题标题】:How do C compilers implement functions that return large structures?C 编译器如何实现返回大型结构的函数?
【发布时间】:2011-01-10 11:23:51
【问题描述】:

函数的返回值通常存储在堆栈或寄存器中。但是对于大型结构,它必须在堆栈上。这段代码在真正的编译器中需要复制多少?还是优化掉了?

例如:

struct Data {
    unsigned values[256];
};

Data createData() 
{
    Data data;
    // initialize data values...
    return data;
}

(假设函数不能内联..)

【问题讨论】:

  • 如果我的编辑不是您想要的,请告诉我。
  • 这确实是一个糟糕的例子,因为“data”本质上是“&data[0]”的同义词。指针是标量。
  • 我更喜欢将此推送给程序员而不是编译器。大对象应始终通过指针传递。这明确表明没有复制大对象。

标签: c compiler-optimization calling-convention abi compiler-theory


【解决方案1】:

但对于大型结构,它必须在 heap 堆栈上。

确实如此!声明为局部变量的大型结构在堆栈上分配。很高兴能解决这个问题。

至于避免复制,正如其他人所指出的那样:

  • 大多数调用约定通过传递一个附加参数来处理“函数返回结构”,该参数指向调用者堆栈框架中应该放置结构的位置。这绝对是调用约定而不是语言的问题。

  • 使用这种调用约定,即使是相对简单的编译器也可以注意到代码路径何时肯定会返回结构,并修复对该结构成员的分配,以便它们直接进入调用者的框架,不必复制。关键是让编译器注意到通过函数的 all 终止代码路径返回 same 结构变量。如果是这种情况,编译器可以安全地使用调用者框架中的空间,而无需在返回点复制。

【讨论】:

  • 实际上有两种不同的 caller-passes-address 约定,这取决于被调用的方法是否需要确保在所有副作用发生后才写入传递的结构,或者调用者是否需要确保传入结构的内容在方法返回之前不会在任何地方可见。在某些情况下,每种方法都有优势。
【解决方案2】:

无;没有完成任何副本。

调用者的Data返回值的地址实际上是作为隐藏参数传递给函数的,createData函数只是简单的写入调用者的栈帧。

这被称为named return value optimisation。另请参阅c++ faq on this topic

商业级 C++ 编译器实现按值返回的方式可以消除开销,至少在简单情况下是这样

...

当 yourCode() 调用 rbv() 时,编译器会秘密传递一个指针,指向 rbv() 应该构造“返回”对象的位置。

你可以通过在你的结构中添加一个带有 printf 的析构函数来证明这一点。如果此按值返回优化正在运行,则只应调用一次析构函数,否则应调用两次。

您还可以检查程序集以查看是否发生这种情况:

Data createData() 
{
    Data data;
    // initialize data values...
    data.values[5] = 6;
    return data;
}

这是程序集:

__Z10createDatav:
LFB2:
        pushl   %ebp
LCFI0:
        movl    %esp, %ebp
LCFI1:
        subl    $1032, %esp
LCFI2:
        movl    8(%ebp), %eax
        movl    $6, 20(%eax)
        leave
        ret     $4
LFE2:

奇怪的是,它在堆栈上为数据项subl $1032, %esp分配了足够的空间,但请注意,它将堆栈上的第一个参数8(%ebp)作为对象的基地址,然后初始化该项目的元素6 .由于我们没有为 createData 指定任何参数,这很奇怪,直到您意识到这是指向父版本 Data 的秘密隐藏指针。

【讨论】:

  • “禁止复制”有点过分了。结构仍然必须被复制。即使“createData()”只修改了 Data 结构中的 1 个元素,整个结构仍然被复制回调用者一次 - 这很容易通过查看生成的程序集来验证。 (至少适用于需要遵守平台 ABI 的非静态函数。更多优化可用于静态函数,或具有这些的编译器的整个程序优化器。
  • 说真的,没有没有复制。我检查了生成的程序集。
  • @all:我们只是说这取决于。因为它确实取决于。上面的简单案例是优化的,在编译时就知道了。尝试用 data.values[i] = rand() 循环填充所有 256 个值。或其他一些外部功能。 gcc(4.4.1 atlest with -O2)会将它全部返回给调用者。 gcc 不会为上述简单案例生成 memcpy。其他编译器可能会有所不同。
  • 更多信息,问题被标记为 C。如果您将代码编译为 C++,gcc 确实不会为结构返回生成副本。 (但如果数组的所有值都是从编译时未知的来源填充的,那么如果将它编译为 C,它
  • 问题被标记为 C,但代码显然是 C++。请注意在每次使用 Data 类型之前缺少的结构。
【解决方案3】:

给出的例子很多,但基本上

这个问题没有明确的答案。这将取决于编译器。

C 没有指定从函数返回的结构体有多大。

这是针对特定编译器的一些测试,即 x86 RHEL 5.4 上的 gcc 4.1.2

gcc 小写,不可复制

[00:05:21 1 ~] $ gcc -O2 -S -c t.c
[00:05:23 1 ~] $ cat t.s
        .file   "t.c"
        .text
        .p2align 4,,15
.globl createData
        .type   createData, @function
createData:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        movl    $1, 24(%eax)
        popl    %ebp
        ret     $4
        .size   createData, .-createData
        .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
        .section        .note.GNU-stack,"",@progbits

gcc 更现实的情况,栈上分配,memcpy 给调用者

#include <stdlib.h>
struct Data {
    unsigned values[256];
};
struct Data createData()
{
    struct Data data;
    int i;
    for(i = 0; i < 256 ; i++)
        data.values[i] = rand();
    return data;
}

[00:06:08 1 ~] $ gcc -O2 -S -c t.c
[00:06:10 1 ~] $ cat t.s
        .file   "t.c"
        .text
        .p2align 4,,15
.globl createData
        .type   createData, @function
createData:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %edi
        pushl   %esi
        pushl   %ebx
        movl    $1, %ebx
        subl    $1036, %esp
        movl    8(%ebp), %edi
        leal    -1036(%ebp), %esi
        .p2align 4,,7
.L2:
        call    rand
        movl    %eax, -4(%esi,%ebx,4)
        addl    $1, %ebx
        cmpl    $257, %ebx
        jne     .L2
        movl    %esi, 4(%esp)
        movl    %edi, (%esp)
        movl    $1024, 8(%esp)
        call    memcpy
        addl    $1036, %esp
        movl    %edi, %eax
        popl    %ebx
        popl    %esi
        popl    %edi
        popl    %ebp
        ret     $4
        .size   createData, .-createData
        .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
        .section        .note.GNU-stack,"",@progbits

gcc 4.4.2### 增长了很多,对于上述不平凡的情况没有复制。

        .file   "t.c"
        .text
        .p2align 4,,15
.globl createData
        .type   createData, @function
createData:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %edi
        pushl   %esi
        pushl   %ebx
        movl    $1, %ebx
        subl    $1036, %esp
        movl    8(%ebp), %edi
        leal    -1036(%ebp), %esi
        .p2align 4,,7
.L2:
        call    rand
        movl    %eax, -4(%esi,%ebx,4)
        addl    $1, %ebx
        cmpl    $257, %ebx
        jne     .L2
        movl    %esi, 4(%esp)
        movl    %edi, (%esp)
        movl    $1024, 8(%esp)
        call    memcpy
        addl    $1036, %esp
        movl    %edi, %eax
        popl    %ebx
        popl    %esi
        popl    %edi
        popl    %ebp
        ret     $4
        .size   createData, .-createData
        .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
        .section        .note.GNU-stack,"",@progbits

另外,VS2008(上面编译为C)会在createData()的栈中保留struct Data,并在Debug模式下执行rep movsd循环将其复制回调用者,在Release模式下它将移动rand() (%eax) 的返回值直接返回给调用者

【讨论】:

    【解决方案4】:
    typedef struct {
        unsigned value[256];
    } Data;
    
    Data createData(void) {
        Data r;
        calcualte(&r);
        return r;
    }
    
    Data d = createData();
    

    msvc(6,8,9) 和 gcc mingw(3.4.5,4.4.0) 将生成类似以下伪代码的代码

    void createData(Data* r) {
          calculate(&r)
    }
    Data d;
    createData(&d);
    

    【讨论】:

    • gcc 将在 createData 函数中为“数据”使用堆栈空间,它不会执行您显示的整个转换,尽管它会将“指针”传递给调用者的结构位置.
    • @nos 标准没有规定返回大型结构的函数的实现方式,所以没有正确答案。我提到的编译器确实会生成这样的代码:在调用者框架上分配空间并将其地址传递给被调用者。
    • 他是不是完全编造了代码?或者这是那些编译器实际发生的事情?他怎么知道的?
    【解决方案5】:

    Linux 上的 gcc 将发出一个 memcpy() 将结构复制回调用者的堆栈中。如果函数具有内部链接,则可以进行更多优化。

    【讨论】:

      猜你喜欢
      • 2014-09-04
      • 2011-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多