以以下简单代码为例:
int foo( int n ) {
return ++n;
}
在MIPS汇编代码中,这可能看起来像这样:foo:
addi $v0, $a0, 1
jr $ra
正如您所看到的,我对n没有使用堆栈。 C ++编译器是否会认识,直接使用CPU的寄存器?
编辑:哇,非常感谢您几乎立即给出详细答案! foo函数体应该是 return ++n;
,而不是 return n++;
。 :)
int foo( int n ) {
return ++n;
}
在MIPS汇编代码中,这可能看起来像这样:foo:
addi $v0, $a0, 1
jr $ra
正如您所看到的,我对n没有使用堆栈。 C ++编译器是否会认识,直接使用CPU的寄存器?
编辑:哇,非常感谢您几乎立即给出详细答案! foo函数体应该是 return ++n;
,而不是 return n++;
。 :)
是的。并不存在"变量总是在栈上分配"这样的规定。C++标准并没有关于栈的规定。它不假设栈存在,也不假设寄存器存在。它仅仅规定代码应该如何运行,而不是如何实现。
编译器只有在必要时才会将变量存储在栈中——例如当它们需要在函数调用后继续存在或者当你尝试获取它们的地址时。
编译器并不愚蠢。
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特有的属性)。
是的,一个好的、优化的C/C++可以优化它。甚至还可以做得更好:参见这里:Felix von Leitners编译器调查。
一个普通的C/C++编译器不会把每个变量都放在栈上。你foo()
函数的问题可能是变量通过栈传递到函数中(你的系统(硬件/操作系统)的ABI定义了这一点)。
使用C语言的register
关键字,您可以给编译器一个提示,建议将变量存储在寄存器中。示例:
register int x = 10;
但请记住:如果编译器不想,它是可以不把x
存储在寄存器中的!
foo:
.frame $sp,0,$ra
.mask 0x00000000,0
.fmask 0x00000000,0
addu $2, $zero, $4
jr $ra
nop
由于你的示例foo
函数是一个身份函数(它只返回其参数),我的C++编译器(VS 2008)完全删除了这个函数调用。如果我将其更改为:
int foo( int n ) {
return ++n;
}
lea edx, [eax+1]
static int foo( int n ) {
return n++;
}
int fee()
{
return foo(5);
}
输出结果为:
.text
.align 2
.globl fee
.ent fee
fee:
.frame $sp,0,$ra
.mask 0x00000000,0
.fmask 0x00000000,0 addiu $2, $zero, 5
jr $ra
nop
.set macro
.set reorder
.end fee
.size fee, .-fee
- Richard Pennington是的,寄存器在C++中被使用。MDR(内存数据寄存器)包含正在获取和存储的数据。例如,要检索单元格123的内容,我们将值123(二进制)加载到MAR中并执行提取操作。操作完成后,单元格123的内容副本将在MDR中。要将值98存储到单元格4中,我们将4加载到MAR中,将98加载到MDR中并执行存储。操作完成后,单元格4的内容将被设置为98,之前存在的任何内容都将被丢弃。数据和地址寄存器与它们一起工作以实现此目的。在C++中,当我们使用一个值初始化变量或询问其值时,也会发生相同的现象。
还有一件事,现代编译器也执行寄存器分配,这比内存分配更快。
gcc -fverbose-asm -O2 -S yoursource.c
命令,然后查看yoursource.s
文件的内容。 - Basile Starynkevitch