register
关键字用C语言做什么?我已经读过它用于优化,但是在任何标准中都没有明确定义。它仍然相关,如果有,您什么时候使用它?
register
变量的地址。
register
关键字用C语言做什么?我已经读过它用于优化,但是在任何标准中都没有明确定义。它仍然相关,如果有,您什么时候使用它?
register
变量的地址。
Answers:
向编译器提示该变量将被大量使用,如果可能,建议将其保存在处理器寄存器中。
大多数现代编译器会自动执行此操作,并且比我们人类更擅长于选择它们。
register
变量的地址;这是关键字的唯一强制性效果register
。即使这样也足以改善优化,因为告诉变量只能在此函数中进行修改变得微不足道。
我很惊讶没有人提到您不能接受寄存器变量的地址,即使编译器决定将变量保留在内存中而不是寄存器中。
因此,使用register
您不会赢得任何收益(无论如何,编译器会自行决定将变量放在何处)而&
不会丢失运算符-没有理由使用它。
register
即使编译器未将其放入寄存器中,它也很有用。
const
也是无用的,因为它不会给您带来任何好处,只会失去更改变量的能力。register
可以用来确保将来没有人不假思索地获取变量的地址。我从来没有理由要使用register
。
它告诉编译器尝试使用CPU寄存器而不是RAM来存储变量。寄存器位于CPU中,并且比RAM访问要快得多。但这只是对编译器的建议,可能不会遵循。
我知道这个问题与C有关,但是C ++的同一问题已被关闭,因为它与该问题完全相同。因此,此答案可能不适用于C。
C ++ 11标准的最新草案N3485在7.1.1 / 3中这样说:
一个
register
说明符是一种提示,如此声明的变量将被频繁使用的实现。[ 注意:提示可以忽略,在大多数实现中,如果使用变量的地址,则将忽略提示。不建议使用此用法...-—尾注 ]
在C ++中(但不是在C),该标准没有规定你不能把一个变量的地址声明register
; 但是,由于在CPU寄存器的整个生命周期中存储的变量都没有与之关联的存储位置,因此尝试获取其地址将无效,并且编译器将忽略register
关键字以允许获取该地址。
实际上,register告诉编译器该变量不会与程序中的其他任何内容(甚至不是char的别名)一起别名。
现代编译器可以在各种情况下利用这一点,并且可以在复杂代码中为编译器提供相当大的帮助-在简单代码中,编译器可以自行解决。
否则,它没有任何作用,也不用于寄存器分配。只要您的编译器足够现代,指定它通常不会导致性能下降。
register
了,根本禁止使用该地址,因为否则可能使编译器知道尽管将变量的地址传递给外部函数,但编译器仍能够应用寄存器优化的情况很有用(变量必须会被刷新到该特定调用的内存中,但是一旦函数返回,编译器便可以再次将其视为一个变量,其地址从未被占用过。
bar
是register
变量,则编译器可以随意将替换foo(&bar);
为int temp=bar; foo(&temp); bar=temp;
,但是bar
在大多数其他情况下,禁止使用的地址似乎并不是一个过于复杂的规则。如果可以将变量保存在寄存器中,则替换将使代码更小。如果仍然需要将该变量保留在RAM中,则替换将使代码变大。在这两种情况下,都不必考虑是否要由编译器来替代,这将导致更好的代码。
register
对全局变量进行限定,无论编译器是否允许使用地址,在循环中重复调用使用全局变量的内联函数的情况下,都可以进行一些不错的优化。我想不出任何其他方法来让该变量在循环迭代之间保存在寄存器中,可以吗?
我已经读过它用于优化,但是在任何标准中都没有明确定义。
事实上,它是由C标准明确规定。引用N1570草案第6.7.1节第6段(其他版本具有相同的措词):
使用存储类说明符声明对象的标识符
register
表明,对对象的访问应尽可能快。此类建议有效的程度由实施定义。
一元运算&
符可能不会应用于用定义的对象register
,也register
可能不会在外部声明中使用。
还有一些其他(相当模糊的)规则专门针对register
-qualified对象:
register
具有未定义的行为。register
,但是您不能对此类对象做任何有用的事情(索引到数组中需要使用其初始元素的地址)。_Alignas
说明符(在C11新)可以不被应用到这样一个对象。va_start
宏的参数名称是register
-qualified,则行为是不确定的。可能还有其他几个。下载标准草案,并在需要时搜索“注册”。
顾名思义,的原始含义register
是要求将对象存储在CPU寄存器中。但是随着优化编译器方面的改进,此功能已不再有用。C标准的现代版本不引用CPU寄存器,因为它们不再(需要)假定存在这种情况(有些体系结构不使用寄存器)。普遍的看法是,应用于register
对象声明更可能会使生成的代码恶化,因为它会干扰编译器自己的寄存器分配。在某些情况下,它仍然很有用(例如,如果您确实确实知道将多久访问一次变量,并且您的知识要比现代优化编译器可以弄清楚的要好)。
的主要实际效果register
是,它可以防止尝试获取对象的地址。这对于作为优化提示不是特别有用,因为它只能应用于局部变量,并且优化编译器可以自己看到未使用该对象的地址。
register
如果您正在考虑的话,则没有限定的数组对象。
register
如果可以合法获取此类变量的地址,则该关键字可以起到有用的作用,但是仅在语义不受影响的情况下,通过将变量的地址复制到临时变量并将其从临时变量中重新加载,语义不会受到影响。在下一个序列点。这将允许编译器假定变量可以安全地保存在所有指针访问的寄存器中,只要在其地址所在的任何位置刷新该变量即可。
讲故事的时间!
C作为一种语言,是计算机的抽象。它允许您根据计算机的功能来做事,即操纵内存,做数学运算,打印事情等。
但是C只是一个抽象。最终,它从您身上提取的是汇编语言。汇编是CPU读取的语言,如果您使用汇编语言,则将根据CPU来执行操作。CPU做什么?基本上,它从内存读取,进行数学运算并写入内存。CPU不仅对内存中的数字进行数学运算。首先,必须将一个数字从一个存储器移到另一个CPU内部的存储器,称为寄存器。一旦完成对该数字的所需处理,就可以将其移回普通系统内存。为什么要完全使用系统内存?寄存器数量有限。在现代处理器中,您只能得到大约一百个字节,而更受欢迎的较旧处理器则受到更大的限制(6502具有3个8位寄存器供您免费使用)。因此,您的平均数学运算如下所示:
load first number from memory
load second number from memory
add the two
store answer into memory
其中很多不是数学。这些加载和存储操作可能会占用您一半的处理时间。C作为计算机的抽象,使程序员免于使用和使用寄存器的麻烦,并且由于计算机之间的数量和类型各不相同,C将寄存器分配的责任完全放在了编译器上。除了一个例外。
声明变量时 register
,您告诉编译器“是的,我打算将此变量大量使用和/或短暂使用。如果您是我,我将尝试将其保存在寄存器中。” 当C标准说编译器实际上不需要执行任何操作时,这是因为C标准不知道您要编译的计算机,就像上面的6502一样,其中所有3个寄存器仅用于操作,并且没有备用寄存器来保留您的电话号码。但是,当它说您不能使用地址时,这是因为寄存器没有地址。他们是处理器的手。由于编译器不必给您地址,并且因为它根本没有地址,因此现在对编译器开放了几种优化方法。可以说,它总是将数字保存在寄存器中。它没有 不必担心它在计算机内存中的存储位置(无需再次将其取回)。它甚至可以将其插入另一个变量,将其提供给另一个处理器,为其提供变化的位置,等等。
tl; dr:存在大量数学的短命变量。不要一次声明太多。
您搞砸了编译器的复杂图形着色算法。这用于寄存器分配。好吧,主要是。它是对编译器的提示-是的。但是请不要完全忽略它,因为不允许您使用寄存器变量的地址(请记住,编译器,请您放心,将尝试以不同的方式进行操作)。在某种程度上告诉您不要使用它。
关键字使用了很久很久了。当只有很少的寄存器可以使用食指将它们全部计数时。
但是,正如我所说,已弃用并不意味着您无法使用它。
进行一些比较(没有任何实际用途)的演示:register
在每个变量之前删除关键字时,这段代码在i7(GCC)上花费3.41秒,而 register
同一代码在0.7秒内完成。
#include <stdio.h>
int main(int argc, char** argv) {
register int numIterations = 20000;
register int i=0;
unsigned long val=0;
for (i; i<numIterations+1; i++)
{
register int j=0;
for (j;j<i;j++)
{
val=j+i;
}
}
printf("%d", val);
return 0;
}
我已经使用以下代码在QNX 6.5.0下测试了register关键字:
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
int main(int argc, char *argv[]) {
uint64_t cps, cycle1, cycle2, ncycles;
double sec;
register int a=0, b = 1, c = 3, i;
cycle1 = ClockCycles();
for(i = 0; i < 100000000; i++)
a = ((a + b + c) * c) / 2;
cycle2 = ClockCycles();
ncycles = cycle2 - cycle1;
printf("%lld cycles elapsed\n", ncycles);
cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
printf("This system has %lld cycles per second\n", cps);
sec = (double)ncycles/cps;
printf("The cycles in seconds is %f\n", sec);
return EXIT_SUCCESS;
}
我得到以下结果:
-> 807679611个周期已过去
->该系统每秒具有3300830000个周期
->以秒为单位的周期为〜0.244600
现在没有注册诠释:
int a=0, b = 1, c = 3, i;
我有:
->经过了1421694077个周期
->该系统每秒具有3300830000个周期
->以秒为单位的周期为〜0.430700
寄存器将通知编译器编码器认为该变量将被足够地写入/读取,以证明其存储在少数可供变量使用的寄存器之一中。从寄存器读/写通常更快,并且可能需要更小的操作码集。
如今,这并不是很有用,因为大多数编译器的优化器在确定是否应将寄存器用于该变量以及使用多长时间方面比您更好。
七十年代,在C语言的最开始,引入了register关键字,以便允许程序员向编译器提供提示,告诉编译器该变量将经常使用,并且应该明智地使用它。将其值保存在处理器的内部寄存器之一中。
如今,优化器比程序员更有效地确定更可能保留在寄存器中的变量,并且优化器并不总是将程序员的提示考虑在内。
许多人错误地建议不要使用register关键字。
让我们看看为什么!
register关键字具有相关的副作用:您不能引用(获取地址)寄存器类型变量。
建议他人不要使用寄存器的人错误地将此作为附加论点。
但是,知道您不能使用寄存器变量的地址这一简单事实,使编译器(及其优化器)知道不能通过指针间接修改此变量的值。
当在指令流的某个点上,将寄存器变量的值分配到处理器的寄存器中,并且自从获取另一个变量的值以来未使用该寄存器时,编译器知道它不需要重新加载该寄存器中变量的值。这可以避免昂贵的无用的内存访问。
进行自己的测试,您将在大多数内部循环中获得显着的性能改进。
在受支持的C编译器上,它尝试优化代码,以使变量的值保存在实际的处理器寄存器中。
Register关键字告诉编译器将特定变量存储在CPU寄存器中,以便可以快速访问它。从程序员的角度来看,register关键字用于程序中大量使用的变量,以便编译器可以加速代码。尽管将变量保留在CPU寄存器还是主存储器中取决于编译器。
gcc 9.3 asm输出,不使用优化标志(此答案中的所有内容均指没有优化标志的标准编译):
#include <stdio.h>
int main(void) {
int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 3
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
#include <stdio.h>
int main(void) {
register int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 8
mov ebx, 3
add ebx, 1
mov esi, ebx
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add rsp, 8
pop rbx
pop rbp
ret
这将强制ebx
用于计算,这意味着需要将其推入堆栈并在函数结束时恢复,因为已保存了被调用者。register
产生更多的代码行,并进行1次内存写入和1次内存读取(尽管实际上,如果esi
使用C ++ 完成计算,则可以将其优化为0 R / Ws const register
)。不使用将register
导致2次写入和1次读取(尽管读取时将发生存储到负载转发)。这是因为该值必须存在并直接在堆栈上更新,以便可以通过地址(指针)读取正确的值。register
没有此要求,因此无法指出。const
和register
是基本上相对的volatile
,并使用volatile
将覆盖文件和块范围内的const优化,以及块范围内的const优化register
。const register
并且register
会产生相同的输出,因为const在块范围内对C不执行任何操作,因此仅register
应用优化。
在clang上,将register
被忽略,但const
仍会进行优化。