函数的返回值通常存储在堆栈或寄存器中。但是对于大型结构,它必须在堆栈上。这段代码在真正的编译器中必须进行多少复制?还是优化了?
例如:
struct Data {
unsigned values[256];
};
Data createData()
{
Data data;
// initialize data values...
return data;
}
(假设函数不能内联..)
函数的返回值通常存储在堆栈或寄存器中。但是对于大型结构,它必须在堆栈上。这段代码在真正的编译器中必须进行多少复制?还是优化了?
例如:
struct Data {
unsigned values[256];
};
Data createData()
{
Data data;
// initialize data values...
return data;
}
(假设函数不能内联..)
没有任何; 没有完成任何副本。
调用者的 Data 返回值的地址实际上是作为隐藏参数传递给函数的,createData 函数只是简单地写入调用者的堆栈帧。
这称为命名返回值优化。另请参阅有关此主题的 c++ 常见问题解答。
商业级 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 的秘密隐藏指针。
但是对于大型结构,它必须在
堆堆栈上。
确实如此!声明为局部变量的大型结构在堆栈上分配。很高兴能澄清这一点。
至于避免复制,正如其他人所指出的:
大多数调用约定通过传递一个附加参数来处理“函数返回结构”,该参数指向调用者堆栈框架中应该放置结构的位置。这绝对是调用约定而不是语言的问题。
使用这种调用约定,即使是相对简单的编译器也有可能注意到代码路径何时肯定会返回一个结构,并且它可以修复对该结构成员的分配,以便它们直接进入调用者的框架而不是' t 必须被复制。关键是让编译器注意到通过函数的所有终止代码路径都返回相同的结构变量。如果是这种情况,编译器可以安全地使用调用者框架中的空间,而无需在返回点复制。
举了很多例子,但基本上
C 没有指定从函数返回的结构体有多大。
这是针对特定编译器的一些测试,即 x86 RHEL 5.4 上的 gcc 4.1.2
[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
#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
.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,并rep movsd
在Debug模式下循环复制回调用者,在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) 和海合会mingw(3.4.5,4.4.0) 将生成类似于以下伪代码的代码
void createData(Data* r) {
calculate(&r)
}
Data d;
createData(&d);
linux 上的 gcc 将发出一个 memcpy() 将结构复制回调用者的堆栈中。如果该函数具有内部链接,则可以进行更多优化。