C中的"注册"关键字?

Nic*_*unt 255 c memory keyword

什么是register关键字在C语言吗?我已经读过它用于优化,但在任何标准中都没有明确定义.它是否仍然相关,如果是,你什么时候使用它?

Bri*_*uch 324

这是编译器的一个提示,即变量将被大量使用,如果可能,建议将其保存在处理器寄存器中.

大多数现代编译器会自动执行此操作,并且比我们人类更擅长选择它们.

  • 一个很好的理由_not_使用'register':你不能把变量的地址称为'register' (77认同)
  • @Euro:您可能知道这一点,但只是为了明确,编译器需要防止"register"变量的地址被采用; 这是`register`关键字的唯一*强制效果.即使这足以改进优化,因为告诉变量只能在此函数中进行修改变得微不足道. (64认同)
  • 请注意,一些/多个编译器将完全忽略register关键字(这是完全合法的). (20认同)
  • 好吧,我尝试使用注册来调整我的ACM提交,有时它确实有帮助.但是你真的必须要很好,因为糟糕的选择会降低性能. (16认同)
  • ypnos:实际上,ACM ICPC问题解决方案的速度更多地取决于算法选择而不是微观优化.5秒的时间限制通常足以提供正确的解决方案,尤其是在使用C而不是Java时. (4认同)
  • 它在嵌入式中仍然非常有用.嵌入式系统的体面编译器甚至允许您设置哪个变量进入哪个寄存器. (2认同)

qrd*_*rdl 63

令我感到惊讶的是,没有人提到你不能获取寄存器变量的地址,即使编译器决定将变量保存在内存而不是寄存器中.

因此,使用register你什么都不赢(无论如何编译器将自己决定放置变量的位置)并失去&操作员 - 没有理由使用它.

  • 实际上有一个原因.事实上,你不能获取变量的地址会产生一些优化机会:编译器可以证明变量不会有别名. (85认同)
  • 通过这个逻辑,`const`也没用,因为它不会赢你,你只能失去改变变量的能力.`register`可以用来确保将来没有人不考虑变量的地址.我从来没有理由使用`register`. (17认同)
  • 众所周知,编译器在证明在非平凡的情况下不会出现混叠是非常糟糕的,因此即使编译器没有将寄存器放入寄存器,`register`对此也很有用. (7认同)
  • @ AlexandreC,Miles,编译器可以很好地检查&是否在任何地方使用了变量。因此,无论在检测锯齿方面有其他困难,重述都无济于事。当K + R首次创建C时,事先*知道将不使用&确实很有用,因为该编译器实际上是在查看以下代码之前,先根据声明来决定寄存器分配的。这就是为什么禁止的地方。'register'关键字现在实际上已经过时了。 (2认同)

And*_*ett 32

它告诉编译器尝试使用CPU寄存器而不是RAM来存储变量.寄存器位于CPU中,访问速度比RAM快得多.但这只是对编译器的一个建议,它可能无法完成.

  • 值得为使用C++的人添加,C++允许您获取寄存器变量的地址 (7认同)
  • @Will:...但编译器最终可能会忽略关键字.看我的回答. (4认同)

bwD*_*aco 21

我知道这个问题是关于C的,但是关于C++的同一个问题被完全复制了.因此,这个答案可能不适用于C.


最新的C++ 11标准草案N3485在7.1.1/3中说明了这一点:

一个register说明符是一种提示,如此声明的变量将被频繁使用的实现.[ 注意:提示可以忽略,在大多数实现中,如果采用变量的地址,它将被忽略.这个用法已被弃用... -end note ]

在C++中(但不是在C中),标准并未声明您不能获取声明的变量的地址register; 但是,因为存储在CPU寄存器中的变量在其整个生命周期中没有与之关联的存储单元,所以尝试获取其地址将无效,并且编译器将忽略该register关键字以允许获取该地址.


Pau*_*lin 16

由于优化者对此做出了比这更好的决策,因此至少15年没有相关性.即使它是相关的,它在具有许多寄存器的CPU架构上更有意义,例如SPARC或M68000,而不是因为缺少寄存器的英特尔,其中大多数寄存器由编译器保留用于其自身目的.


Kei*_*son 13

我已经读过它用于优化,但在任何标准中都没有明确定义.

事实上,它由C标准明确定义的.引用N1570草案第6.7.1节第6段(其他版本具有相同的措辞):

具有存储类说明符的对象的标识符声明register表明对对象的访问尽可能快.这些建议有效的程度是实施定义的.

一元运算&符可能不适用于定义的对象register,register也不能用于外部声明.

还有一些其他(相当模糊的)规则是特定于register限定对象的:

  • 定义register具有未定义行为的数组对象.
    更正:定义数组对象是合法的register,但是你不能对这样的对象做任何有用的事情(索引到数组需要获取其初始元素的地址).
  • _Alignas说明符(在C11新)可以不被应用到这样一个对象.
  • 如果传递给va_start宏的参数名称是register-qualified,则行为是未定义的.

可能还有其他几个; 如果您有兴趣,请下载标准草案并搜索"注册".

顾名思义,该原始的含义register是要求的对象将被存储在一个CPU寄存器.但随着优化编译器的改进,这已变得不那么有用了.C标准的现代版本不涉及CPU寄存器,因为它们不再(需要)假设存在这样的事情(存在不使用寄存器的架构).常识是,应用于register对象声明更有可能使生成的代码恶化,因为它会干扰编译器自己的寄存器分配.可能仍然存在一些有用的情况(例如,如果您确实知道变量的访问频率,并且您的知识优于现代优化编译器可以解决的问题).

主要的实际效果register是它可以防止任何尝试获取对象的地址.这不是特别有用的优化提示,因为它只能应用于局部变量,并且优化编译器可以看到自己没有采用这样的对象的地址.


小智 12

实际上,register告诉编译器该变量不与程序中的任何其他内容(甚至不是char)进行别名.

现代编译器可以在各种情况下利用它,并且可以在复杂的代码中帮助编译器 - 在简单的代码中,编译器可以自己解决这个问题.

否则,它没有用处,也不用于寄存器分配.只要编译器足够现代,它通常不会导致性能下降.

  • @greggo:允许对全局变量进行“注册”限定,无论编译器是否允许获取地址,都可以在循环中重复调用使用全局变量的内联函数的情况下进行一些很好的优化。我想不出任何其他方法可以让该变量在循环迭代之间保存在寄存器中 - 你可以吗? (2认同)

Ori*_*ion 7

讲故事的时间!

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
Run Code Online (Sandbox Code Playgroud)

很多是......不是数学.这些加载和存储操作可能需要一半的处理时间.C,作为计算机的抽象,使程序员免于使用和处理寄存器的担忧,并且由于计算机之间的数量和类型不同,C将寄存器分配的责任完全放在编译器上.有一个例外.

当你声明一个变量时register,你告诉编译器"哟,我打算让这个变量被大量使用和/或短暂存在.如果我是你,我会尝试将它保存在寄存器中." 当C标准说编译器不必实际做任何事情时,那是因为C标准不知道你正在编译什么计算机,它可能就像上面的6502那样,只需操作所有3个寄存器,并没有备用寄存器来保存您的电话号码.但是,当它说你无法获取地址时,那是因为寄存器没有地址.他们是处理器的手.由于编译器不必为您提供地址,并且由于它根本不能有地址,因此现在可以对编译器进行多次优化.例如,它可以始终将数字保存在寄存器中.它不必担心它存储在计算机内存中的位置(除了需要再次将其恢复).它甚至可以将它变成另一个变量,将它交给另一个处理器,给它一个不断变化的位置等等.

tl; dr:执行大量数学运算的短期变量.不要一次声明太多.


dir*_*tly 5

您正在使用编译器复杂的图形着色算法。这用于寄存器分配。嗯,主要是。它充当对编译器的提示——这是真的。但不能完全忽略,因为您不允许获取寄存器变量的地址(请记住,编译器,现在任您摆布,会尝试以不同的方式行事)。这在某种程度上告诉你不要使用它。

关键字使用了很久很久。当只有很少的寄存器可以用你的食指计算它们时。

但是,正如我所说,弃用并不意味着您不能使用它。

  • 一些较旧的硬件比现代英特尔机器拥有更多的寄存器。寄存器数量与年龄无关,而与 CPU 架构有关。 (13认同)
  • @JUSTMYcorrectOPINION 实际上,X86 基本上总共有六个,最多留下 1 或 2 个用于“注册”。事实上,由于已经编写或移植了如此多的代码到一个缺少寄存器的机器上,我怀疑这对“寄存器”关键字成为安慰剂有很大贡献——在没有寄存器时提示寄存器毫无意义。4 年多之后,幸好 x86_64 已将其提升到 14,而 ARM 现在也很重要。 (2认同)

Rob*_*ert 5

只是一个小的演示(没有任何真实世界的目的)作比较:在取出时,register每个变量之前的关键字,这段代码发生在我的i7处理器(GCC)3.41秒, register0.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;
}
Run Code Online (Sandbox Code Playgroud)

  • 使用 gcc 4.8.4 和 -O3,我没有区别。如果没有 -O3 和 40000 次迭代,我在 1.5 秒的总时间内得到了 * 可能 * 少 50 毫秒,但我没有运行足够多的时间来知道这是否具有统计意义。 (3认同)