C ++ CPU寄存器用法

时间:2009-12-02 12:28:15

标签: c++ compiler-construction assembly mips cpu-registers

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

例如,使用以下简单代码:

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

在MIPS汇编程序代码中,这可能如下所示:

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

正如您所看到的,我根本不需要使用堆栈。 C ++编译器会识别出来并直接使用CPU的寄存器吗?

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

6 个答案:

答案 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或询问其值时,会发生相同的现象。

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