在C ++中,局部变量总是在堆栈上分配。堆栈是应用程序可以占用的允许内存的一部分。该内存保存在RAM中(如果没有换成磁盘)。现在,C ++编译器是否总是创建在堆栈中存储局部变量的汇编程序代码?
例如,使用以下简单代码:
int foo( int n ) {
return ++n;
}
在MIPS汇编程序代码中,这可能如下所示:
foo:
addi $v0, $a0, 1
jr $ra
正如您所看到的,我根本不需要使用堆栈。 C ++编译器会识别出来并直接使用CPU的寄存器吗?
编辑:哇,非常感谢您几乎立即和广泛的答案! foo的函数体当然应该是return ++n;
,而不是return n++;
。 :)
答案 0 :(得分:12)
是。没有规则“变量总是在堆栈上分配”。 C ++标准没有说明堆栈。它不假设堆栈存在,或存在寄存器。它只是说代码应该如何表现,而不是如何实现。
编译器只在必要时将变量存储在堆栈中 - 例如,当它们必须通过函数调用时,或者如果你试图获取它们的地址。
编译器不是傻瓜。 ;)
答案 1 :(得分: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特定属性。)
答案 2 :(得分:8)
是的,一个好的,优化的C / C ++将优化它。甚至 MUCH 更多:See here: Felix von Leitners Compiler Survey。
普通的C / C ++编译器无论如何都不会将每个变量放在堆栈上。 foo()
函数的问题可能是变量可以通过堆栈传递给函数(系统的ABI(硬件/操作系统)定义了)。
使用C的register
关键字,您可以为编译器提供提示,将变量存储在寄存器中可能会更好。样品:
register int x = 10;
但请记住:编译器可以自由地将x
存储在寄存器中,如果它想要的话!
答案 3 :(得分:6)
答案是肯定的,也许吧。它取决于编译器,优化级别和目标处理器。
对于mips,前四个参数(如果很小)在寄存器中传递,返回值在寄存器中返回。因此,您的示例无需在堆栈上分配任何内容。
实际上,真相比小说更奇怪。在您的情况下,参数返回不变:返回的值是++运算符之前的n:
foo:
.frame $sp,0,$ra
.mask 0x00000000,0
.fmask 0x00000000,0
addu $2, $zero, $4
jr $ra
nop
答案 4 :(得分:2)
由于您的示例foo
函数是一个标识函数(它只返回它的参数),我的C ++编译器(VS 2008)完全删除了这个函数调用。如果我将其更改为:
int foo( int n ) {
return ++n;
}
编译器使用
进行内联lea edx, [eax+1]
答案 5 :(得分:0)
是,寄存器用于C ++。 MDR(存储器数据寄存器)包含被提取和存储的数据。例如,为了检索单元格123的内容,我们将值123(以二进制形式)加载到MAR中并执行获取操作。当操作完成时,单元123的内容的副本将在MDR中。为了将值98存储到单元格4中,我们将4加载到MAR中,将98加载到MDR中并执行存储。当操作完成时,通过丢弃之前的任何内容,单元4的内容将被设置为98。数据&地址寄存器与它们一起工作以实现此目的在C ++中,当我们使用值初始化var或询问其值时,会发生相同的现象。
而且,还有一件事,现代编译器也执行寄存器分配,这比内存分配更快。