2013-10-31 124 views
2

您好我一直在阅读这些东西在不同的文档为什么编译器将变量存储在寄存器中?

寄存器

告诉编译器来存储变量在CPU寄存器正在申报。

在标准C方言,关键字寄存器使用以下语法:

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

register int i; 请注意,当开启优化时,TIGCC会自动将经常使用的变量存储在CPU寄存器中,但关键字寄存器将强制存储在寄存器中,即使关闭优化。然而,如果编译器认为在这个地方没有足够的空闲寄存器供使用,那么在寄存器中存储数据的请求可能会被拒绝。

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

+1

我不明白你在问什么。变量必须在寄存器中才能执行指令。这正是CPU工作的方式。 – Kevin

+1

目前还不清楚你究竟在问什么。您正在阅读的文档讨论编译器生成的代码。即来自您的C代码,编译器需要生成告诉CPU如何操作的代码,其中包括变量驻留在内存中的位置,执行指令时使用哪个CPU寄存器等等。一旦编译器生成了该代码,就会在运行编译代码时发生实际的内存/寄存器分配和使用。 – nos

回答

5

编译器生成机器码是?机器码运行以实际运行你的程序。所以编译器决定它生成的机器代码,因此它决定了在运行时会发生什么样的分配。当你输入gcc foo.c时,它不会执行它们,但是当你运行可执行文件时,它会生成正在运行的代码GCC。

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

+0

除此之外,'register'不会强制编译器将变量实际分配给一个寄存器,但这只是一个有用的指示。如果我读到的一些评论是真实的,那么现代编译器就是这样优化的,它们在猜测哪些变量作为“注册”更有用,而且大多忽略关键字时做得更好。 – SJuan76

+0

任何编译器都是一样的。运行时总是运行实际业务的代码,它只是运行编译器生成的代码(可能在运行时会发生很少的决定),但没有其他语言的文档说编译器会这样做并执行此操作。他们说编译器生成代码,运行时运行代码。即使使用解释器语言也是如此。我知道c生成机器代码,但我的观点是编译器只是将源代码编译成机器可理解的,但如果实际执行必须发生,它必须运行。为什么C文档与其他文档有如此不同? –

+0

@MuralidharYaragalla因为在每个C编译器中编译器都决定生成那些代码。这就像是说“指挥官要攻击那个岛”,指挥官可能不会亲自去做,但他给出的命令是 – jozefg

1

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

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是全球性的(因此编译器没有按”不知道其他人是否可以在运行期间访问他)。

在GCC编译与-O3产生以下代码为主要:

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 <[email protected]> 
    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 <[email protected]> 
    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 <[email protected]> 
    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中)。这个例子用来说明使用寄存器时会有多好,以及有时候不能。

+0

谢谢你。它确实有帮助。 –

相关问题