C++ CPU寄存器使用情况

发布于 2024-08-13 14:47:43 字数 453 浏览 2 评论 0原文

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

以下面的简单代码为例:

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

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

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

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

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

In C++, local variables are always allocated on the stack. The stack is a part of the allowed memory that your application can occupy. That memory is kept in your RAM (if not swapped out to disk). Now, does a C++ compiler always create assembler code that stores local variables on the stack?

Take, for example, the following simple code:

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

In MIPS assembler code, this could look like this:

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

As you can see, I didn't need to use the stack at all for n. Would the C++ compiler recognize that, and directly use the CPU's registers?

Edit: Wow, thanks a lot for your almost immediate and extensive answers! The function body of foo should of course be return ++n;, not return n++;. :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

好菇凉咱不稀罕他 2024-08-20 14:47:43

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

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

编译器并不傻。 ;)

Yes. There is no rule that "variables are always allocated on the stack". The C++ standard says nothing about a stack.It doesn't assume that a stack exists, or that registers exist. It just says how the code should behave, not how it should be implemented.

The compiler only stores variables on the stack when it has to - when they have to live past a function call for example, or if you try to take the address of them.

The compiler isn't stupid. ;)

三寸金莲 2024-08-20 14:47:43

免责声明:我不了解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 特定的属性。)

Disclaimer: I don't know MIPS, but I do know some x86, and I think the principle should be the same..

In the usual function call convention, the compiler will push the value of n onto the stack to pass it to the function foo. However, there is the fastcall convention that you can use to tell gcc to pass the value through the registers instead. (MSVC also has this option, but I'm not sure what its syntax is.)

test.cpp:

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

Compiling the above with g++ -O3 -fomit-frame-pointer -c test.cpp, I get for foo1:

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

As you can see, it reads in the value from the stack.

And here's foo2:

lea eax,[ecx+0x1]
ret

Now it takes the value directly from the register.

Of course, if you inline the function the compiler will do a simple addition in the body of your larger function, regardless of the calling convention you specify. But when you can't get it inlined, this is going to happen.

Disclaimer 2: I am not saying that you should continually second-guess the compiler. It probably isn't practical and necessary in most cases. But don't assume it produces perfect code.

Edit 1: If you are talking about plain local variables (not function arguments), then yes, the compiler will allocate them in the registers or on the stack as it sees fit.

Edit 2: It appears that calling convention is architecture-specific, and MIPS will pass the first four arguments on the stack, as Richard Pennington has stated in his answer. So in your case you don't have to specify the extra attribute (which is in fact an x86-specific attribute.)

末蓝 2024-08-20 14:47:43

是的,一个好的、优化的 C/C++ 会优化它。甚至还有更多:请参阅此处:Felix von Leitners 编译器调查

无论如何,普通的 C/C++ 编译器不会将每个变量都放入堆栈中。 foo() 函数的问题可能是变量可以通过堆栈传递给函数(系统(硬件/操作系统)的 ABI 定义了这一点)。

使用 C 的 register 关键字,您可以给编译器一个提示,表明将变量存储在寄存器中可能会更好。示例:

register int x = 10;

但请记住:如果编译器愿意,它可以自由地不将 x 存储在寄存器中!

Yes, a good, optimizing C/C++ will optimize that. And even MUCH more: See here: Felix von Leitners Compiler Survey.

A normal C/C++ compiler will not put every variable on the stack anyway. The problem with your foo() function could be that the variable could get passed via the stack to the function (the ABI of your system (hardware/OS) defines that).

With C's register keyword you can give the compiler a hint that it would probably be good to store a variable in a register. Sample:

register int x = 10;

But remember: The compiler is free not to store x in a register if it wants to!

枫以 2024-08-20 14:47:43

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

对于 mips,前四个参数(如果很小)会在寄存器中传递,返回值会在寄存器中返回。因此,您的示例不需要在堆栈上分配任何内容。

事实上,事实比小说更离奇。在您的情况下,参数返回不变:返回的值是 ++ 运算符之前的 n 的值:

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

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

The answer is yes, maybe. It depends on the compiler, the optimization level, and the target processor.

In the case of the mips, the first four parameters, if small, are passed in registers and the return value is returned in a register. So your example has no requirement to allocate anything on the stack.

Actually, truth is stranger than fiction. In your case the parameter is returned unchanged: the value returned is that of n before the ++ operator:

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

    addu    $2, $zero, $4
    jr      $ra
    nop
孤城病女 2024-08-20 14:47:43

由于您的示例 foo 函数是一个恒等函数(它仅返回其参数),因此我的 C++ 编译器 (VS 2008) 完全删除了此函数调用。如果我将其更改为:

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

编译器将其内联为

lea edx, [eax+1] 

Since your example foo function is an identity function (it just returns it's argument), my C++ compiler (VS 2008) completely removes this function call. If I change it to:

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

the compiler inlines this with

lea edx, [eax+1] 
寄居人 2024-08-20 14:47:43

是的,C++ 中使用了寄存器。 MDR(内存数据寄存器)包含正在获取和存储的数据。例如,要检索单元格 123 的内容,我们会将值 123(二进制)加载到 MAR 中并执行获取操作。操作完成后,单元格 123 的内容的副本将位于 MDR 中。为了将值 98 存储到单元格 4 中,我们将 4 加载到 MAR 中,将 98 加载到 MDR 中并执行存储。当操作完成时,单元格 4 的内容将被设置为 98,并丢弃之前的内容。数据&地址寄存器与它们一起实现这一目标。在 C++ 中也是如此,当我们用一个值初始化 var 或询问它的值时,会发生相同的现象。

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

Yes, The registers are used in C++. The MDR (memory data registers) contains the data being fetched and stored. For example, to retrieve the contents of cell 123, we would load the value 123 (in binary) into the MAR and perform a fetch operation. When the operation is done, a copy of the contents of cell 123 would be in the MDR. To store the value 98 into cell 4, we load a 4 into the MAR and a 98 into the MDR and perform a store. When the operation is completed the contents of cell 4 will have been set to 98, by discarding whatever was there previously. The data & address registers work with them to achieve this. In C++ too, when we initialize a var with a value or ask its value, the same phenomena Happens.

And, One More Thing, Modern Compilers also perform Register Allocation, which is kinda faster than memory allocation.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文