在C ++中注册关键字


88

之间有什么区别

int x=7;

register int x=7;

我正在使用C ++。


8
@GMan:ANSI C不允许使用寄存器对象的地址;此限制不适用于C ++
Brian R. Bondy 2010年

1
@布莱恩:嗯,你是对的。现在只是在注释中(如果使用该地址,它可能会被忽略),但是没有强制性。很高兴知道。(嗯,有点。:P)
GManNickG 2010年

8
register在C和C ++之间,重新打开投票的语义不同。
CB Bailey 2010年

3
因此,在C语言中,可以通过创建数组寄存器来禁止数组到指针的转换:register int a[1];使用该声明,您将无法对该数组建立​​索引。如果尝试,您会做UB
Johannes Schaub-litb 2010年

2
确实,我投票决定重新开放。在知道有区别之前,我投票关闭。
GManNickG

Answers:


24

在2010年存在的C ++中,任何使用关键字“自动”或“注册”的有效程序在语义上都将与删除了这些关键字的程序相同(除非它们出现在字符串宏或其他类似的上下文中)。从这种意义上讲,关键字对于正确编译程序毫无用处。另一方面,关键字可能在某些宏上下文中很有用,以确保对宏的不正确使用将导致编译时错误,而不是生成伪代码。

在C ++ 11和更高版本的语言中,该auto关键字被重新用作充当已初始化对象的伪类型,编译器将自动将其替换为初始化表达式的类型。因此,在C ++ 03中,声明:auto int i=(unsigned char)5;等同int i=5;于在块上下文中使用,并且auto i=(unsigned char)5;违反了约束。在C ++ 11中,与等效auto int i=(unsigned char)5;auto i=(unsigned char)5;成为约束违例auto unsigned char i=5;


22
最后一位的示例可能会有用。
丹尼斯·齐克福斯

14
自2011年以来,该答案不再正确,auto不能简单地省略关键字。也许您可以更新答案。
Walter

2
@沃尔特:你能引用发生了什么变化吗?我没有关注所有的语言更改。
超级猫2014年

2
@supercat,是的,暂时,但是register已被弃用,并且将有一个建议将其删除以用于C ++ 17。
Jonathan Wakely 2014年

3
根据en.cppreference.com/w/cpp/language/auto的介绍,C ++ 11之后auto的版本现在用于自动类型推断,但是在此之前,它用于指定您希望变量“自动”存储(因此,我猜是在堆栈上)而不是关键字register(意思是“处理器的寄存器”):
Guillaume

96

register 是对编译器的提示,建议编译器将该变量存储在处理器寄存器中而不是内存中(例如,而不是堆栈)。

编译器可能会也可能不会遵循该提示。

根据Herb Sutter在“没有的关键字(或其他名称的注释)”中的说法:

寄存器说明符与自动说明符具有相同的语义。


2
从C ++ 17开始,已弃用,未使用和保留它。
ZachB

@ZachB,这是不正确的;寄存器在C ++ 17中是保留的,但它的工作原理和功能几乎与C的寄存器相同。
Lewis Kelsey

@LewisKelsey在C ++ 17规范中未使用并保留;它不是storage-class-specifier语法中的语法之一,也没有定义的语义。合格的编译器会像Clang一样抛出错误。但是,某些实现仍然允许它,或者忽略它(MSVC,ICC)或将其用作优化提示(GCC)。参见open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html。但是我在一点上没有说错:在C ++ 11中已弃用。
ZachB


25

对于当今的编译器,可能什么也没有。最初是将变量放置在寄存器中以加快访问速度的提示,但如今大多数编译器都忽略了该提示并自行决定。


9

几乎可以肯定没有。

register提示您计划使用x很多编译器,并且您认为应该将其放置在寄存器中。

但是,与普通(甚至是专家)程序员相比,编译器现在在确定应在寄存器中放置什么值方面要好得多,因此,编译器只是忽略关键字,然后执行所需的操作。



7

register关键字是有用的:

  • 内联汇编。
  • 专业的C / C ++编程。
  • 可缓存变量声明。

一个生产系统的示例,其中register需要关键字:

typedef unsigned long long Out;
volatile Out out,tmp;
Out register rax asm("rax");
asm volatile("rdtsc":"=A"(rax));
out=out*tmp+rax;

从C ++ 11开始不推荐使用,并且在C ++ 17中未使用和保留。


2
而且我想补充一下,'register'关键字仅对运行单个C ++程序且没有线程且没有多任务的微控制器有用。C ++程序必须拥有整个CPU,以确保不会将“ register”变量从特殊的CPU寄存器中移出。
圣地亚哥·比利亚弗尔特

您要添加@SantiagoVillafuerte来编辑答案吗?
ncomputers

我不确定我的回答...虽然听起来似乎合理。我希望将其保留为评论,以便其他人批准或不批准。
圣地亚哥·比利亚弗尔特

1
@SantiagoVillafuerte在多任务系统中,上下文切换操作系统(而不是应用程序)负责保存/恢复寄存器时,这实际上是不正确的。由于您不必在每条CPU指令后进行上下文切换,因此将内容放入寄存器中绝对有意义。这里的其他答案(编译器根本不在乎您对寄存器分配的意见)在此更为准确。
立方

您显示的示例实际上是使用GCC的Explicit Register Variables扩展名,该扩展名register存储类说明符不同,并且仍受GCC支持。
ZachB

1

考虑一种情况,编译器的优化器有两个变量,被迫将一个变量洒到堆栈上。碰巧两个变量对编译器的权重相同。如果没有区别,编译器将随意溢出其中一个变量。另一方面,register关键字为编译器提供了一个提示,该提示将更频繁地访问该变量。它类似于x86预取指令,但用于编译器优化程序。

显然,register提示类似于用户提供的分支概率提示,并且可以从这些概率提示中推断出来。如果编译器知道某个分支经常被使用,它将把与分支相关的变量保留在寄存器中。因此,我建议更多地关注分支提示,而不要去关注register。理想情况下,您的探查器应该以某种方式与编译器进行通信,并使您不必考虑这种细微差别。


1

从gcc 9.3开始,使用进行编译-std=c++2a register生成编译器警告,但它仍然有预期的效果,其行为同样到C的register在尊重Ofast优化标志-没有-O1编译时这个答案。但是,使用clang ++-7会导致编译器错误。因此,是的,register优化仅对没有优化-O标志的标准编译有所不同,但它们是编译器即使使用-O1也会发现的基本优化。

唯一的区别是在C ++中,您可以使用寄存器变量的地址,这意味着仅当您不使用变量的地址或其别名(创建指针)或采用引用时,才进行优化它在代码中的位置(仅在-O0上,因为引用也有一个地址,因为它是堆栈上的const指针,就像使用-Ofast进行编译时可以在堆栈外优化的指针一样,除非它们永远不会出现使用-Ofast在堆栈上,因为与指针不同,它们不能被创建volatile并且它们的地址不能被使用),否则它的行为就像您没有使用过的一样register,并且该值将存储在堆栈中。

在-O0上,另一个区别是const register在gcc C和gcc C ++上的行为不同。在gcc C上,const register其行为类似于register,因为const未在gcc上优化block-scope 。在C语言C上,register不执行任何操作,仅应用const块范围优化。在gcc C上进行了register优化,但const在大范围内没有优化。在gcc C ++上,优化registerconst块范围优化结合在一起。

#include <stdio.h> //yes it's C code on C++
int main(void) {
  const register int i = 3;
  printf("%d", i);
  return 0;
}

int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3
  mov eax, DWORD PTR [rbp-4]
  mov esi, eax
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

register int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  push rbx
  sub rsp, 8
  mov ebx, 3
  mov esi, ebx
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  mov rbx, QWORD PTR [rbp-8] //callee restoration
  leave
  ret

const int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3 //still saves to stack
  mov esi, 3 //immediate substitution
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

const register int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  mov esi, 3 //loads straight into esi saving rbx push/pop and extra indirection (because C++ block-scope const is always substituted immediately into the instruction)
  mov edi, OFFSET FLAT:.LC0 // can't optimise away because printf only takes const char*
  mov eax, 0 //zeroed: https://stackoverflow.com/a/6212755/7194773
  call printf
  mov eax, 0 //default return value of main is 0
  pop rbp //nothing else pushed to stack -- more efficient than leave (rsp == rbp already)
  ret

register告诉编译器1)在这种情况下rbx,将本地变量存储在被调用方保存的寄存器中,以及2)如果从未使用过变量的地址,则优化堆栈写操作const告诉编译器立即替换该值(而不是为其分配寄存器或从内存中加载值),并将本地变量作为默认行为写入堆栈。const register是这些勇敢的优化的结合。这是尽可能的苗条。

另外,在gcc C和C ++上,register似乎自己创建了一个堆栈上的第一个本地堆栈在堆栈上随机的16字节间隙,这不会发生const register

但是使用-Ofast进行编译;register具有0个优化效果,因为如果可以将其放在寄存器中或立即将其保留,那么它将始终存在,如果不能,则不会存在;const仍然优化了C和C ++的负载,但是仅在文件范围内; volatile仍然强制值从堆栈中存储和加载。

.LC0:
  .string "%d"
main:
  //optimises out push and change of rbp
  sub rsp, 8 //https://stackoverflow.com/a/40344912/7194773
  mov esi, 3
  mov edi, OFFSET FLAT:.LC0
  xor eax, eax //xor 2 bytes vs 5 for mov eax, 0
  call printf
  xor eax, eax
  add rsp, 8
  ret
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.