2009-12-02 181 views
10

在C++中,局部变量总是在堆栈上分配。堆栈是您的应用程序可以占用的允许内存的一部分。该内存保存在你的RAM中(如果没有换出到磁盘)。现在,C++编译器是否总是创建汇编代码来存储栈上的局部变量?C++ CPU寄存器的使用

举个例子,下面简单的代码:

int foo(int n) { 
    return ++n; 
} 

在MIPS汇编代码,这可能是这样的:

foo: 
addi $v0, $a0, 1 
jr $ra 

正如你所看到的,我也没必要完全可以使用堆栈。 C++编译器是否可以识别并直接使用CPU的寄存器?

编辑:哇,非常感谢您的几乎直接和广泛的答案! foo的函数体当然应该是return ++n;,而不是return n++;。 :)

+0

编译器会优化。尝试使用'gcc -fverbose-asm -O2 -S yoursource.c'然后在'yoursource.s'里面查看'yoursource.s' – 2012-10-12 05:59:41

回答

9

免责声明:我不知道MIPS,但我确实知道一些X86,而且我认为原则应该是一样的..

在平时的函数调用约定,编译器会推n值到堆栈上传递给函数foo。但是,您可以使用fastcall约定来告诉gcc通过寄存器来传递值。 (MSVC也有这个选项,但我不确定它的语法是什么。)

test.cpp:

int foo1 (int n) { return ++n; } 
int foo2 (int n) __attribute__((fastcall)); 
int foo2 (int n) { 
    return ++n; 
} 

编译以上g++ -O3 -fomit-frame-pointer -c test.cpp,我得到foo1

mov eax,DWORD PTR [esp+0x4] 
add eax,0x1 
ret 

正如你可以看到,它读取从堆栈中值。

而这里的foo2

lea eax,[ecx+0x1] 
ret 

现在从寄存器直接取值。

当然,如果你内联函数,编译器会做一个简单的加法在较大的函数体中,无论您指定的调用约定。但是,当你无法将其内联时,这将会发生。

2免责声明:我不是说你应该不断地在事后的编译器。在大多数情况下,这可能不切实际和必要。但不要认为它会产生完美的代码。

编辑1:如果你在谈论普通的局部变量(而不是函数参数),那么是的,编译器会根据它认为合适的将它们分配到寄存器或堆栈中。

编辑2:看起来,调用约定是特定于架构的,MIPS将通过堆栈中的前四个参数,正如Richard Pennington在他的回答中所述。所以你的情况,你不必指定额外的属性(这实际上是一个特定的x86属性。)

+1

-O在不影响调试的机器上禁用堆栈帧设置 - x86 isn其中一个,你需要一个单独的-fomit-frame-pointer来消除'冗余'堆栈帧设置(这实际上对调试有用,即在堆栈帧展开时) – matja 2009-12-02 13:00:07

+0

是的,我完全忘记了这一点。我会解决它。但差异仍然存在。 – int3 2009-12-02 13:05:29

+0

执行链接时间优化的编译器也可以认识到,由于可以看到并修复所有呼叫站点,因此可以将呼叫全部转换为快速呼叫。 – 2009-12-02 13:06:20

12

是的。没有规则“变量总是在堆栈上分配”。 C++标准没有提到堆栈。它不认为堆栈存在,或者存在寄存器。它只是说代码应该如何表现,而不是应该如何实现。

编译器只在需要时才在栈上存储变量 - 例如,当它们必须经过函数调用时,或者如果您试图获取它们的地址。

编译器不傻。 ;)

8

是的,一个很好的优化C/C++会优化。甚至更多更多:See here: Felix von Leitners Compiler Survey

正常的C/C++编译器不会把每个变量放在堆栈上。您的foo()函数的问题可能是变量可能通过堆栈传递给函数(系统的ABI(硬件/操作系统)定义了该函数)。

用C的register关键字,您可以给编译器暗示,这可能会是很好的一个变量保存在寄存器中。示例:

register int x = 10; 

但要记住:编译器是免费不保存x在寄存器中,如果要!

+0

+1链接 – 2009-12-19 10:49:59

6

答案是肯定的,也许。它取决于编译器,优化级别和目标处理器。

在MIPS的情况下,前四个参数中,如果小,在寄存器传递和返回值被在寄存器返回。所以你的例子没有要求在堆栈上分配任何东西。

其实,事实比虚构更离奇。你的情况的参数原封不动地返回:返回的值是该++前n个运营商:

foo: 
    .frame $sp,0,$ra 
    .mask 0x00000000,0 
    .fmask 0x00000000,0 

    addu $2, $zero, $4 
    jr  $ra 
    nop 
2

因为你的榜样foo功能是标识功能(它只是返回它的参数),我的C++编译器( VS 2008)完全删除了这个函数调用。如果我将其更改为:

int foo(int n) { 
    return ++n; 
} 

编译器内联这与

lea edx, [eax+1] 
+0

是的,再次以mips为例: static int foo(int n){ return n ++; } int fee() { return foo(5); } 给出: 的.text .align伪2 .globl费 .ENT费 费: .frame $ SP,0,$ RA .mask 0x00000000,0 .fmask 0x00000000,0 addiu $ 2 ,$零,5 JR $ RA NOP .SET宏观 .SET重新排序 .END费 .size费。收费 – 2009-12-02 12:53:44

0

是,该寄存器C被使用++。 MDR(存储器数据寄存器)包含正在读取和存储的数据。例如,要检索单元格123的内容,我们会将值123(二进制)加载到MAR中并执行提取操作。操作完成后,单元格123内容的副本将位于MDR中。要将值98存储到单元格4中,我们将3加载到MAR中,将98加载到MDR中并执行存储。操作完成后,单元格4的内容将被设置为98,通过丢弃先前的内容。数据&地址寄存器与他们合作来实现这一点。在C++中,当我们用一个值初始化一个var或者询问它的值时,同样的现象就会发生。

而且,还有一件事,现代编译器也执行寄存器分配,它比内存分配还快。