为什么编译器将变量存储在寄存器中

时间:2013-10-31 14:49:49

标签: c compiler-construction

您好,我一直在阅读各种文档中的这类内容

  

寄存器

     

告诉编译器将声明的变量存储在CPU寄存器中。

     

在标准C语言中,关键字注册使用以下语法:

     

注册数据定义;   寄存器类型修饰符告诉编译器将声明的变量存储在> CPU寄存器(如果可能)中,以优化访问。例如,

     

注册int i;   请注意,当打开优化时,TIGCC会自动将常用变量存储在CPU寄存器中,但关键字寄存器将强制存储在寄存器中,即使>如果关闭了优化。然而,如果编译器断定没有足够的空闲寄存器用于>这个地方,则可以拒绝在寄存器中存储数据的请求。

我的观点不仅仅是注册。我的观点是为什么编译器会将变量存储在内存中。编译器业务只是编译和生成目标文件。在运行时,实际的内存分配发生。为什么编译器会做这项业务。我的意思是没有运行目标文件只是通过编译文件本身在c的情况下发生内存分配吗?

3 个答案:

答案 0 :(得分:5)

编译器正在生成机器代码,机器代码用于运行程序。编译器决定它生成什么机器代码,因此决定在运行时将发生什么类型的分配。当您键入gcc foo.c时,它不会执行它们,但稍后,当您运行可执行文件时,它是生成的代码GCC正在运行。

这意味着编译器希望生成尽可能快的代码并在编译时做出尽可能多的决策,这包括如何分配内容。

答案 1 :(得分:1)

编译器需要将代码转换为机器指令,并告诉计算机如何运行代码。这包括如何进行操作(如乘以两个数字)以及如何存储数据(堆栈,堆或寄存器)。

答案 2 :(得分:1)

编译器不运行代码(除非它进行了几轮分析和更好的代码执行),但它必须准备它 - 这包括如何保持程序定义的变量,是否使用快速和有效存储为寄存器,或使用较慢(更容易产生副作用)的存储器。

最初,您的局部变量将被简单地分配给堆栈帧上的位置(当然除了显式使用动态分配的内存)。如果您的函数分配了一个int,您的编译器可能会告诉堆栈增加几个额外的字节并使用该内存地址存储该变量并将其作为操作数传递给您的代码对该变量进行的任何操作。
但是,由于内存较慢(即使在缓存时),并且操作它会对CPU造成更多限制,在稍后阶段,编译器可能会决定尝试将一些变量移动到寄存器中。这种分配是通过一个复杂的算法来完成的,该算法试图选择最适合你的体系结构所具有的逻辑寄存器数量的重用和延迟关键变量(同时通过各种限制确认,例如某些指令要求操作数在此或注册)。

还有另一个复杂因素 - 某些内存地址可能会以编译时未知的方式与外部指针混淆,在这种情况下,您无法将它们移动到寄存器中。编译器通常是一个非常谨慎的组合,大多数都会避免危险的优化(否则他们需要进行一些特殊检查以避免讨厌的事情)。

毕竟,编译器仍然有礼貌,让你建议哪个变量对你很重要,如果他错过了它,并用register标记这些变量关键字你基本上要求他尝试通过使用寄存器来优化这个变量,只要有足够的寄存器可用,并且不存在别名。

以下是一个小例子:采取以下代码,两次做同样的事情,但情况略有不同:

#include "stdio.h"

int j;
int main() {
    int i;
    for (i = 0; i < 100; ++i) {
        printf ("i'm here to prevent the loop from being optimized\n");
    }
    for (j = 0; j < 100; ++j) {
        printf ("me too\n");
    }
}

请注意,我是本地的,j是全局的(因此编译器不知道是否有其他人可能在运行期间访问他)。

使用-O3在gcc中进行编译会为main生成以下代码:

0000000000400540 <main>:
  400540:       53                      push   %rbx
  400541:       bf 88 06 40 00          mov    $0x400688,%edi
  400546:       bb 01 00 00 00          mov    $0x1,%ebx
  40054b:       e8 18 ff ff ff          callq  400468 <puts@plt>
  400550:       bf 88 06 40 00          mov    $0x400688,%edi
  400555:       83 c3 01                add    $0x1,%ebx            # <-- i++
  400558:       e8 0b ff ff ff          callq  400468 <puts@plt>
  40055d:       83 fb 64                cmp    $0x64,%ebx
  400560:       75 ee                   jne    400550 <main+0x10>
  400562:       c7 05 80 04 10 00 00    movl   $0x0,1049728(%rip)        # 5009ec <j>
  400569:       00 00 00
  40056c:       bf c0 06 40 00          mov    $0x4006c0,%edi
  400571:       e8 f2 fe ff ff          callq  400468 <puts@plt>
  400576:       8b 05 70 04 10 00       mov    1049712(%rip),%eax        # 5009ec <j> (loads j)
  40057c:       83 c0 01                add    $0x1,%eax            # <-- j++
  40057f:       83 f8 63                cmp    $0x63,%eax
  400582:       89 05 64 04 10 00       mov    %eax,1049700(%rip)        # 5009ec <j> (stores j back)
  400588:       7e e2                   jle    40056c <main+0x2c>
  40058a:       5b                      pop    %rbx
  40058b:       c3                      retq

如您所见,第一个循环计数器位于ebx中,并在每次迭代时递增并与限制进行比较。
然而第二个循环是危险的,gcc决定通过内存传递索引计数器(每次迭代将其加载到rax中)。这个例子用来说明你在使用寄存器时的表现有多好,以及有时你不能这样做。