是否将按值传递的结构推入堆栈?

时间:2013-08-03 22:05:29

标签: c++ c compiler-construction

c / c ++编译器是否按值将结构推送到堆栈上,如果程序员指定了一个大型结构,则将数百个字节存储到堆栈中?返回的结构会受到同样的惩罚吗?

2 个答案:

答案 0 :(得分:5)

是的,大多数编译器会推送堆栈,或复制到堆栈值传递结构(和class es)。 ABI(应用程序二进制接口)规范通常需要相对于编译器和放大器的规范。处理器和操作系统。

参见例如X86 calling conventions& 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 probegcc -fdump-tree-all来理解内部(Gimple)表示。

请注意,C ++中一些非常复杂的class可能具有较小的值大小,因为内部涉及很多指针。例如,在Linux / AMD64上sizeof(std::string)只是一个(8字节)字(包含指向某些复杂内容的指针),它可能通过寄存器传递。同样,C ++标准库的许多容器具有较小的值大小(大多数实际数据通过指针间接访问)。细节显然是特定于实现的。

答案 1 :(得分:1)

是的,编译器几乎肯定会像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以及%ecx%rsi%rdi(分别为计数,来源和目的地)的设置。 26美元是它将复制的8字节单元的数量。 26 * 8 = 208字节。 %rsp是堆栈指针。这几乎就是memcpy如果以简单形式内联的样子[实际memcpy最有可能有一大堆额外的工作来处理未对齐的开始/结束和使用SSE指令等]。

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函数被内联),并且很难看到发生了什么。