c/c++ 编译器是否按值将结构推入堆栈,如果程序员指定一个大结构,则将数百个字节复制到堆栈上?返回结构是否会产生相同的惩罚?
2 回答
是的,大多数编译器确实推送堆栈,或复制到堆栈值传递结构(和class
es)。ABI (应用程序二进制接口)规范通常要求它与编译器、处理器和操作系统相关。
有关详细信息,请参见例如X86 调用约定和System V ABI x86-64(至少对于 Linux,x86-64)。
实际上,大型结构位于堆栈上,通过寄存器(无声地)传递指向它们的指针。
ABI 定义这些结构是在调用者还是被调用者调用框架中......
对于两个字大小的struct
-s,Linux x86-64 ABI 经常通过一对寄存器传递它们(作为参数和结果)。
使用GCC,尝试编译使用gcc -O -S -fverbose-asm foo.c
以获取汇编代码foo.s
;您也可以使用GCC MELT 探针或gcc -fdump-tree-all
了解内部(Gimple)表示。
请注意,C++ 中一些相当复杂class
的值可能具有较小的值大小,因为内部涉及大量指针。例如,在 Linux/AMD64sizeof(std::string)
上只有一个(8 字节)字(包含指向一些复杂内容的指针),它可能通过寄存器传递。同样,许多 C++ 标准库的容器具有较小的值大小(大部分真实数据通过指针间接访问)。细节显然是特定于实现的。
是的,编译器几乎肯定会执行类似 memcpy 的操作,将数百字节的结构或类复制到堆栈中,如果这是您要求的。如果不是这种情况,这样的事情就行不通:
std::string s = "A large amount of text";
std::string r = rev(s);
std::cout << s << " reversed is " << r << std::endl;
...
std::string rev(std::string s)
{
std::string::size_type len = s.length();
for(std::string::size_type i = 0; i < len / 2; i++)
{
swap(s[i], s[len-i]);
}
return s;
}
这就是为什么几乎总是建议尽可能使用const
引用,因为它只传递一个指向对象的指针。
既然上面的例子遭到反对,下面是另一个例子:
class mystring
{
char s[200];
size_t len;
public:
mystring(const char *aS)
{
strcpy(s, aS);
len = strlen(s);
}
char& operator[](int index)
{
return s[index];
}
size_t length()
{
return len;
}
}
mystring str("Some long string");
mystring rev = rev_my_str(s);
mystring rev_my_str(mystring s)
{
size_t len = s.length();
for(size_t i = 0; i < len / 2; i++)
{
swap(s[i], s[len-i]);
}
return s;
}
事实上,这将为mystring
堆栈上的两个对象腾出空间,一个用于s
进入rev_my_str
,一个用于返回值。
编辑:
g++ -O1
[1] 为上述调用生成的汇编程序rev_my_string
。有趣的一点是,和(分别是计数、源和目标)rep movsq
的设置。$26 是它将复制的 8 字节单元的数量。26 * 8 = 208 字节。是堆栈指针。如果它以简单的形式内联,这几乎就是它的外观[实际上很可能有一大堆额外的工作来处理未对齐的开始/结束和使用 SSE 指令等]。%ecx
%rsi
%rdi
%rsp
memcpy
memcpy
movl $26, %ecx
movq %rsp, %rdi
movq %rbx, %rsi
rep movsq
leaq 416(%rsp), %rdi
call _Z10rev_my_str8mystring
rev_my_string 本身看起来像这样。注意rep movsq
函数底部的 。这就是它存储结果字符串的地方。
_Z10rev_my_str8mystring:
.LFB990:
.cfi_startproc
movq %rdi, %rax
movq 208(%rsp), %r9
movq %r9, %r10
shrq %r10
je .L5
addq $1, %r10
movl $1, %edx
.L6:
movl %r9d, %ecx
subl %edx, %ecx
leaq 7(%rsp), %rsi
addq %rdx, %rsi
movzbl (%rsi), %edi
movslq %ecx, %rcx
movzbl 8(%rsp,%rcx), %r8d
movb %r8b, (%rsi)
movb %dil, 8(%rsp,%rcx)
addq $1, %rdx
cmpq %r10, %rdx
jne .L6
.L5:
movl $26, %ecx
movq %rax, %rdi
leaq 8(%rsp), %rsi
rep movsq
ret
[1] 使用比这更高的优化会使编译器内联过多的代码(例如,rev_my_string 函数被内联),并且很难看到发生了什么。