在C语言中,“register”关键字可以提示编译器将变量存储在CPU寄存器中,以便更快地访问它们并提高程序效率。然而,现代编译器通常可以根据上下文自行选择最佳存储位置,因此“register”关键字很少使用。
这是向编译器提供的一个提示,表明变量将被频繁使用,并建议尽可能将其保存在处理器寄存器中。
大多数现代编译器会自动执行此操作,并且比我们人类更擅长选择它们。
我很惊讶没有人提到过,即使编译器决定将变量存储在内存中而不是寄存器中,也不能获取寄存器变量的地址。
因此,使用 register
没有任何好处(无论如何编译器都会决定变量放在哪里),同时失去了 &
运算符 - 没有理由使用它。
register
对于此仍然很有用。 - Miles Rout该语句告诉编译器尝试使用CPU寄存器而不是RAM来存储变量。寄存器位于CPU中,访问速度比RAM快得多。但这只是对编译器的建议,它可能不一定遵循。
我知道这个问题涉及到C语言,但是同样关于C++的问题被关闭了,认为是这个问题的一个精确重复。因此,这个答案可能不适用于C。
C++11标准的最新草案N3485在7.1.1/3中指出:
register
限定符是向实现者提示所声明的变量将会被频繁使用。[ 注意: 如果变量的地址被取出,则该提示可以被忽略,在大多数实现中,如果变量的地址被取出,它也将被忽略。这种用法已经过时... —end note ]
在C++中(但不是C),标准没有规定您不能取一个被声明为register
的变量的地址;然而,由于存储在CPU寄存器中的变量在其生命周期内没有与之关联的内存位置,尝试取其地址将是无效的,编译器将忽略register
关键字以允许取其地址。
register
声明标识符来表示访问对象应尽可能快。这样的建议在多大程度上有效是由实现定义的。”不能对使用register
定义的对象应用一元运算符&
,也不能在外部声明中使用register
。还有一些其他(相当晦涩的)规则是特定于使用register
修饰的对象的。
register
定义数组对象具有未定义的行为。register
定义数组对象是合法的,但您不能对这样的对象进行任何有用的操作(索引到数组需要获取其初始元素的地址)。_Alignas
说明符(C11中的新说明符)可能无法应用于此类对象。va_start
宏的参数名称被标记为register
,则其行为是未定义的。还可能有其他一些,请下载标准草案并搜索"register"以了解更多信息。
作为其名称所示,register
的原始含义是要求将对象存储在 CPU 寄存器中。但随着优化编译器的改进,这变得不再有用。现代版本的 C 标准不再引用 CPU 寄存器,因为它们不再(需要)假定存在这样的东西(有一些架构不使用寄存器)。普遍认为,将 register
应用于对象声明更可能会恶化生成的代码,因为它干扰了编译器自己的寄存器分配。仍然可能有一些情况是有用的(比如,如果您确实知道一个变量被访问的频率有多高,并且您的知识比现代优化编译器能够推断的更好)。
register
的主要切实影响是防止尝试获取对象的地址。这并不特别有用作为优化提示,因为它只能应用于局部变量,并且优化编译器可以自行看到这样的对象的地址没有被获取。register
限定的数组对象,那就错了。 - Keith Thompsonregister
数组对象的理解是错误的;请查看我的回答中更新的第一条要点。定义这样的对象是合法的,但你不能对其进行任何操作。如果在 你的示例 中将 register
添加到 s
的定义中,则该程序在 C 中是非法的(违反了约束)。C++ 对 register
没有相同的限制,因此该程序将是有效的 C++(但使用 register
是没有意义的)。 - Keith Thompsonregister
关键字变量的地址,那么它可能会有用处,但仅限于在获取其地址时将变量复制到临时变量中,且在下一个序列点从临时变量重新加载时不会影响语义的情况下。这将允许编译器假定该变量可以安全地保留在寄存器中,跨所有指针访问,只要在获取其地址的任何位置刷新即可。 - supercat在至少15年的时间里,它已经不再相关了,因为优化器可以比你更好地做出这方面的决策。即使在它仍然相关的时候,在像SPARC或M68000这样具有大量寄存器的CPU架构上比在Intel上更加合理,后者只有很少的寄存器,大多数被编译器保留用于自身目的。
实际上,register告诉编译器该变量在程序中与其他任何东西(甚至是char)都没有别名。
这可以在现代编译器中的各种情况下被利用,并且可以帮助编译器在复杂代码中大有裨益——在简单代码中,编译器可以自行解决这个问题。
否则,它没有任何作用,也不用于寄存器分配。只要您的编译器足够现代化,指定它通常不会导致性能下降。
register
禁止获取地址,否则它可以让编译器知道即使变量的地址被传递给外部函数,编译器仍能应用寄存器优化的情况(该变量必须在特定调用中刷新到内存,但一旦函数返回,编译器可以再次将其视为从未获取过地址的变量)。 - supercatbar
是一个register
变量,编译器可以随意将foo(&bar);
替换为int temp=bar; foo(&temp); bar=temp;
,但在大多数其他情况下,取bar
的地址是被禁止的,这似乎不是一个过于复杂的规则。如果变量本来就可以保留在寄存器中,那么替换会使代码更小。如果变量无论如何都需要保留在RAM中,替换会使代码更大。让编译器决定是否进行替换将在两种情况下都产生更好的代码。 - supercat故事时间!
C语言是计算机的抽象。它允许您做一些与计算机相关的事情,如操作内存、进行数学运算、打印等。
但C只是一个抽象。最终,它从您那里提取的是汇编语言。汇编语言是CPU读取的语言,如果您使用它,您将按照CPU的方式执行操作。CPU做什么?基本上是从内存中读取数据,进行数学运算,并将结果写回内存。CPU不会直接对内存中的数字进行数学运算。首先,您需要将数字从内存移动到CPU内部称为寄存器的内存中。完成对此数字所需的任何操作后,您可以将其移回常规系统内存。为什么要使用系统内存?寄存器数量有限。现代处理器只有约100个字节,而早期流行的处理器甚至更加奇葩(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;
}