C编译器如何实现返回大型结构的函数?

Ste*_*nov 24 c compiler-theory abi calling-convention compiler-optimization

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

例如:

struct Data {
    unsigned values[256];
};

Data createData() 
{
    Data data;
    // initialize data values...
    return data;
}
Run Code Online (Sandbox Code Playgroud)

(假设函数无法内联..)

Ale*_*own 23

没有; 没有副本完成.

调用者的数据返回值的地址实际上作为隐藏参数传递给函数,而createData函数只是写入调用者的堆栈帧.

这称为命名返回值优化.另请参阅有关此主题c ++ faq.

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

...

当yourCode()调用rbv()时,编译器秘密地将指针传递给rbv()应该构造"返回"对象的位置.

您可以通过向struct添加一个带有printf的析构函数来证明这已经完成.如果这个按值返回优化正在运行,则只应调用析构函数一次,否则只调用两次.

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

Data createData() 
{
    Data data;
    // initialize data values...
    data.values[5] = 6;
    return data;
}
Run Code Online (Sandbox Code Playgroud)

这是集会:

__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:
Run Code Online (Sandbox Code Playgroud)

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

  • 说真的,没有复制*.我检查了生成的程序集. (7认同)
  • 更多信息,问题被标记C.如果您将代码编译为C++,gcc确实不会生成struct返回的副本.(但是*如果你将它编译为C,如果数组的所有值都是从编译时未知的源填充的话,它*将* (4认同)

Nor*_*sey 7

但是,对于一个大的结构,它是在栈.

确实如此!声明为局部变量的大型结构在堆栈上分配.很高兴有这个清理.

至于避免复制,正如其他人所说:

  • 大多数调用约定通过传递一个额外的参数来处理"函数返回结构",该参数指向应该放置结构的调用者的堆栈帧中的位置.这绝对是调用约定而不是语言的问题.

  • 使用这种调用约定,即使是一个相对简单的编译器也可以注意到代码路径肯定会返回一个结构,并且它可以修复该结构的成员的赋值,以便它们直接进入调用者的框架并且不要必须复制.关键是编译器注意到通过该函数的所有终止代码路径都返回相同的 struct变量.如果是这种情况,编译器可以安全地使用调用者框架中的空间,从而无需在返回时使用副本.


lee*_*roy 6

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

这个问题没有任何明确的答案.它取决于编译器.

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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