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

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

例如:

struct Data { unsigned values[256]; }; Data createData() { Data data; // initialize data values... return data; } 

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

没有; 没有副本完成。

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

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

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

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

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

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

 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)上的第一个参数8(%ebp)作为对象的基地址,然后初始化该元素6项目。 因为我们没有为createData指定任何参数,所以这很奇怪,直到你意识到这是指向父版本Data的秘密隐藏指针。

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

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

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

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

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

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

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

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

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

gcc琐碎的案例,没有复制

 [00:05:21 1 ~] $ gcc -O2 -S -c tc [00:05:23 1 ~] $ cat ts .file "tc" .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  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 tc [00:06:10 1 ~] $ cat ts .file "tc" .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 "tc" .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并执行rep movsd循环以在调试模式下将其复制回调用者,在Release模式下它将移动rand的返回值()(%eax)直接返回调用者

 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); 

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