Registrar palavra-chave em C ++

89

Qual é a diferença entre

int x=7;

e

register int x=7;

?

Estou usando C ++.

dato datuashvili
fonte
8
@GMan: ANSI C não permite obter o endereço de um objeto de registro; esta restrição não se aplica a C ++
Brian R. Bondy
1
@Brian: Hm, você está certo. Está apenas em uma nota agora (que provavelmente será ignorado se o endereço for usado), mas não obrigatório. Bom saber. (Bem, mais ou menos.: P)
GManNickG
8
A votação para reabrir registertem semânticas diferentes entre C e C ++.
CB Bailey
3
como conseqüência disso, em C é possível proibir a conversão de array em ponteiro fazendo um registro de array: register int a[1];com essa declaração, você não pode indexar esse array. Se você tentar, você faz UB
Johannes Schaub - litb 08/07/10
2
Na verdade, votei para reabrir. Votei pelo fechamento antes de saber que havia uma diferença.
GManNickG

Respostas:

24

Em C ++ como existia em 2010, qualquer programa válido que use as palavras-chave "auto" ou "registrar" será semanticamente idêntico a um com as palavras-chave removidas (a menos que apareçam em macros string ou outros contextos semelhantes). Nesse sentido, as palavras-chave são inúteis para programas de compilação adequada. Por outro lado, as palavras-chave podem ser úteis em certos contextos de macro para garantir que o uso impróprio de uma macro causará um erro em tempo de compilação em vez de produzir código falso.

Em C ++ 11 e versões posteriores da linguagem, a autopalavra-chave foi redefinida para atuar como um pseudo-tipo para objetos que são inicializados, os quais um compilador irá substituir automaticamente pelo tipo da expressão de inicialização. Assim, em C ++ 03, a declaração: auto int i=(unsigned char)5;era equivalente a int i=5;quando usada dentro de um contexto de bloco e auto i=(unsigned char)5;era uma violação de restrição. No C ++ 11, auto int i=(unsigned char)5;tornou-se uma violação de restrição enquanto auto i=(unsigned char)5;se tornou equivalente a auto unsigned char i=5;.

supergato
fonte
22
Um exemplo do último bit pode ser útil.
Dennis Zickefoose
14
Essa resposta não é mais correta, desde 2011, a palavra-chave autonão pode ser simplesmente omitida ... Talvez você possa atualizar sua resposta.
Walter
2
@Walter: Você pode citar o que mudou? Não acompanhei todas as mudanças de idioma.
supercat de
2
@supercat, sim, por enquanto, mas registerestá obsoleto e haverá uma proposta para removê-lo do C ++ 17.
Jonathan Wakely
3
De acordo com en.cppreference.com/w/cpp/language/auto , post C ++ 11, autoagora é usado para dedução automática de tipo. Mas antes disso, era usado para especificar que você queria que sua variável fosse armazenada "automaticamente" ( portanto, na pilha, eu acho) em oposição à palavra-chave register(que significa "registro do processador"):
Guillaume
96

register é uma dica para o compilador, aconselhando-o a armazenar essa variável em um registro do processador em vez de na memória (por exemplo, em vez da pilha).

O compilador pode ou não seguir essa dica.

De acordo com Herb Sutter em "Palavras-chave que não são (ou comentários com outro nome)" :

Um especificador de registro tem a mesma semântica de um especificador automático ...

Tom
fonte
2
Desde C ++ 17, ele está obsoleto, não usado e reservado, no entanto.
ZachB
@ZachB, isso está incorreto; O registro é reservado no C ++ 17, mas ainda funciona e funciona quase de forma idêntica ao registro do C.
Lewis Kelsey de
@LewisKelsey Não é usado e está reservado na especificação C ++ 17; não é um dos storage-class-specifierda gramática e não tem semântica definida. Um compilador em conformidade pode gerar um erro como o Clang faz. No entanto, algumas implementações ainda permitem e ignoram (MSVC, ICC) ou usam como uma dica de otimização (GCC). Consulte open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html . Eu falei errado em um ponto: ele foi descontinuado no C ++ 11.
ZachB,
25

Com os compiladores de hoje, provavelmente nada. Originalmente, era uma dica para colocar uma variável em um registrador para acesso mais rápido, mas a maioria dos compiladores hoje ignora essa dica e decide por si.

KeithB
fonte
9

Quase certamente nada.

registeré uma dica para o compilador que você planeja usar xmuito e que você acha que deveria ser colocado em um registrador.

No entanto, os compiladores agora são muito melhores em determinar quais valores devem ser colocados nos registradores do que o programador médio (ou mesmo especialista), então os compiladores simplesmente ignoram a palavra-chave e fazem o que desejam.

James Curran
fonte
7

A registerpalavra-chave foi útil para:

  • Montagem embutida.
  • Programação especialista em C / C ++.
  • Declaração de variáveis ​​armazenáveis ​​em cache.

Um exemplo de sistema produtivo, onde a registerpalavra - chave foi necessária:

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

Ele está obsoleto desde C ++ 11 e não é usado e é reservado no C ++ 17 .

ncomputadores
fonte
2
E eu acrescentaria que a palavra-chave 'registrar' só seria útil em um microcontrolador executando um único programa C ++ sem threads e sem multitarefa. O programa C ++ teria que possuir toda a CPU para garantir que a variável de 'registro' não fosse movida dos registros especiais da CPU.
Santiago Villafuerte
@SantiagoVillafuerte deseja adicioná-lo editando a resposta?
ncomputers
Não tenho certeza da minha resposta ... embora pareça plausível. Prefiro deixar como um comentário para que outras pessoas o aprovem ou desaprovem.
Santiago Villafuerte
1
@SantiagoVillafuerte Isso não é verdade, em sistemas multitarefa quando a mudança de contexto do SO - não da aplicação - é responsável por salvar / restaurar registros. Já que você não está trocando de contexto após cada instrução da CPU, colocar coisas em registradores é absolutamente significativo. As outras respostas aqui (que os compiladores simplesmente não se importam com sua opinião quando se trata de alocação de registros) são mais precisas.
Cubic de
O exemplo que você mostrou está, na verdade, usando a extensão Explicit Register Variables do GCC , que é diferente do registerespecificador de classe de armazenamento e ainda é suportado pelo GCC.
ZachB de
2

A partir do gcc 9.3, compilar usando -std=c++2a, register produz um aviso do compilador, mas ainda tem o efeito desejado e se comporta de forma idêntica ao C registerao compilar sem sinalizadores de otimização -O1 –- Ofast em relação a esta resposta. Usar clang ++ - 7 causa um erro do compilador. Portanto, sim, registerotimizações só fazem diferença na compilação padrão sem sinalizadores -O de otimização, mas são otimizações básicas que o compilador descobriria mesmo com -O1.

A única diferença é que em C ++, você pode pegar o endereço da variável de registro, o que significa que a otimização só ocorre se você não pegar o endereço da variável ou seus apelidos (para criar um ponteiro) ou tomar uma referência dele no código (somente em - O0, porque uma referência também tem um endereço, porque é um ponteiro const na pilha , que, como um ponteiro pode ser otimizado fora da pilha se for compilado usando -Ofast, exceto que eles nunca aparecerão na pilha usando -Ofast, porque ao contrário de um ponteiro, eles não podem ser feitos volatilee seus endereços não podem ser pegos), caso contrário, ele se comportará como você não usou registere o valor será armazenado na pilha.

Em -O0, outra diferença é que const registerem gcc C e gcc C ++ não se comportam da mesma forma. No gcc C, const registerse comporta como register, porque os escopos de bloco constnão são otimizados no gcc. No clang C, registernão faz nada e somente constas otimizações de escopo de bloco se aplicam. No gcc C, as registerotimizações se aplicam, mas constno escopo do bloco não há otimização. No gcc C ++, tanto registere constotimizações bloco de escopo de combinar.

#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

registerdiz ao compilador para 1) armazenar uma variável local em um registro salvo do callee, neste caso rbx, e 2) otimizar as gravações da pilha se o endereço da variável nunca for obtido . constdiz ao compilador para substituir o valor imediatamente (em vez de atribuir a ele um registro ou carregá-lo da memória) e gravar a variável local na pilha como comportamento padrão. const registeré a combinação dessas otimizações encorajadas. Isso é o mais fino possível.

Além disso, no gcc C e C ++, registerpor si só parece criar um intervalo aleatório de 16 bytes na pilha para o primeiro local na pilha, o que não acontece com const register.

Compilando usando -Ofast no entanto; registertem 0 efeito de otimização porque se puder ser colocado em um cadastro ou tornado imediato, sempre será e se não puder não será; constainda otimiza a carga em C e C ++, mas apenas no escopo do arquivo ; volatileainda força os valores a serem armazenados e carregados da pilha.

.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
Lewis Kelsey
fonte
1

Considere um caso em que o otimizador do compilador tem duas variáveis ​​e é forçado a espalhar uma na pilha. Acontece que ambas as variáveis ​​têm o mesmo peso para o compilador. Dado que não há diferença, o compilador irá espalhar arbitrariamente uma das variáveis. Por outro lado, a registerpalavra - chave dá ao compilador uma dica de qual variável será acessada com mais frequência. É semelhante à instrução de pré-busca x86, mas para otimizador de compilador.

Obviamente, as registerdicas são semelhantes às dicas de probabilidade de ramos fornecidas pelo usuário e podem ser inferidas a partir dessas dicas de probabilidade. Se o compilador souber que algum branch é usado com freqüência, ele manterá as variáveis ​​relacionadas ao branch nos registradores. Portanto, sugiro que se preocupe mais com as dicas de branch e esquecê-las register. O ideal é que o seu criador de perfil se comunique de alguma forma com o compilador e evite que você pense nessas nuances.

SmugLispWeenie
fonte