Palavra-chave "register" em C?


273

O que a registerpalavra - chave faz na linguagem C? Eu li que ele é usado para otimizar, mas não está claramente definido em nenhum padrão. Ainda é relevante e, se sim, quando você o usaria?


42
O que a palavra-chave register faz em C? é ignorado :)
bestsss

19
@bestsss Não completamente ignorado. Tente obter um endereço de registervariável.
Qdl

4
Esse código que você está lendo é antigo youtube.com/watch?v=ibF36Yyeehw#t=1827
Coronel Panic

Respostas:


341

É uma dica para o compilador de que a variável será muito usada e que você recomenda que seja mantida em um registro do processador, se possível.

A maioria dos compiladores modernos faz isso automaticamente e é melhor escolhê-los do que nós, humanos.


18
Bem, experimentei o registro para ajustar meus envios do ACM, e às vezes isso realmente ajudou. Mas você realmente precisa ser cuidadoso, porque más escolhas prejudicam o desempenho.
ypnos 23/02/09

81
Um bom motivo não usar 'registrar': você não pode tomar o endereço de uma variável declarada 'registrar'
Adam Rosenfield

23
Observe que alguns / muitos compiladores ignoram completamente a palavra-chave register (o que é perfeitamente legal).
23610 Euro Micelli

5
ypnos: Na verdade, a velocidade de uma solução para problemas de ACM ICPC depende muito mais da escolha do algoritmo do que dessas micro otimizações. O limite de 5 segundos geralmente é suficiente para uma solução correta, especialmente ao usar C em vez de Java.
10139 Joey

66
@ Euro: Você provavelmente sabe disso, mas apenas para ser explícito, o compilador é necessário para impedir que o endereço de uma registervariável seja usado; esse é o único efeito obrigatório da registerpalavra - chave. Mesmo isso é suficiente para melhorar as otimizações, porque torna-se trivial dizer que a variável só pode ser modificada nessa função.
Dale Hagglund

69

Estou surpreso que ninguém tenha mencionado que você não pode usar um endereço da variável de registro, mesmo que o compilador decida manter a variável na memória e não no registro.

Portanto, usando registervocê não ganha nada (de qualquer forma, o compilador decide por si próprio onde colocar a variável) e perde o &operador - não há razão para usá-la.


94
Existe uma razão, na verdade. O simples fato de você não poder usar um endereço da variável gera algumas oportunidades de otimização: o compilador pode provar que a variável não terá alias.
Alexandre C.

8
Os compiladores são notoriamente péssimos em provar que o alias não ocorre em casos não triviais; portanto, registeré útil para isso, mesmo que o compilador não o coloque em um registro.
Route de milhas

2
@AlexandreC, Miles, compiladores estão perfeitamente bem em verificar se & é tirado de uma variável em qualquer lugar. Portanto, independentemente de outras dificuldades em detectar aliases, reafirmar que você não compra nada. Quando o K + R criou C, foi realmente útil saber com antecedência que & não seria usado, pois o compilador realmente tomou a decisão de alocação de registro ao ver a declaração, antes de examinar o código a seguir. É por isso que a proibição está em vigor. A palavra-chave 'register' é essencialmente obsoleta agora.
Greggo

25
Por essa lógica, consttambém é inútil, pois não ganha nada, você perde a capacidade de alterar uma variável. registerpode ser usado para garantir que ninguém use o endereço de uma variável no futuro sem pensar. Eu nunca tive motivos para usar registerembora.
precisa

34

Ele diz ao compilador para tentar usar um registro da CPU, em vez da RAM, para armazenar a variável. Os registros estão na CPU e são muito mais rápidos de acessar que a RAM. Mas é apenas uma sugestão para o compilador, e pode não seguir adiante.


8
Vale acrescentar para pessoas que usam C ++, C ++ permite que você leve o endereço de uma variável registo
Will

5
@ Will: ... mas o compilador provavelmente acabará ignorando a palavra-chave como resultado. Veja minha resposta.
bwDraco

Sim, parece que 'register' é um placebo em C ++, existe apenas para permitir que o código C seja compilado como C ++. E não faria muito sentido proibir o & var, permitindo passar por referência ou const-reference, e sem passar por referência, você quebrou seriamente o C ++.
22715 greggo

22

Eu sei que esta pergunta é sobre C, mas a mesma pergunta para C ++ foi encerrada como uma duplicata exata dessa pergunta. Portanto, esta resposta pode não se aplicar a C.


O último rascunho do padrão C ++ 11, N3485 , diz isso em 7.1.1 / 3:

Um registerespecificador é uma dica para a implementação de que a variável assim declarada será muito usada. [ note: A dica pode ser ignorada e, na maioria das implementações, será ignorada se o endereço da variável for utilizado. Este uso está obsoleto ... - enviar nota ]

Em C ++ (mas não em C), o padrão não indica que você não pode usar o endereço de uma variável declarada register; no entanto, como uma variável armazenada em um registro da CPU durante toda a sua vida útil não possui um local de memória associado, a tentativa de obter seu endereço seria inválida e o compilador ignorará a registerpalavra-chave para permitir a obtenção do endereço.


17

Não é relevante há pelo menos 15 anos, pois os otimizadores tomam melhores decisões sobre isso do que você pode. Mesmo quando era relevante, fazia muito mais sentido em uma arquitetura de CPU com muitos registros, como SPARC ou M68000, do que na Intel com sua escassez de registros, a maioria dos quais são reservados pelo compilador para seus próprios fins.


13

Na verdade, o registrador informa ao compilador que a variável não faz alias com mais nada no programa (nem mesmo caracteres).

Isso pode ser explorado pelos compiladores modernos em várias situações e pode ajudar bastante o compilador em código complexo - em código simples, os compiladores podem descobrir isso sozinhos.

Caso contrário, não serve para nada e não é usado para alocação de registros. Geralmente, não ocorre degradação no desempenho para especificá-lo, desde que o seu compilador seja moderno o suficiente.


"diz ao compilador .." não, não diz. Todas as variáveis ​​automáticas possuem essa propriedade, a menos que você pegue seu endereço e o use de maneiras que excedam certos usos analisáveis. Portanto, o compilador sabe disso a partir do código, independentemente de você usar a palavra-chave register. Acontece que a palavra-chave 'register' torna ilegal escrever essa construção, mas se você não usar a palavra-chave e não aceitar o endereço dessa maneira, o compilador ainda saberá que é seguro. Essa informação é crucial para a otimização.
22715 greggo

1
@greggo: Pena que registerproíbe usar o endereço, pois, caso contrário, poderia ser útil informar aos compiladores os casos em que um compilador seria capaz de aplicar otimizações de registro, apesar do endereço de uma variável ser passado para uma função externa (a variável teria que ser liberado na memória para essa chamada específica , mas quando a função retornar, o compilador poderá tratá-la novamente como uma variável cujo endereço nunca havia sido utilizado).
Supercat 26/05

@ supercat Eu acho que ainda seria uma conversa muito complicada de se ter com o compilador. Se é isso que você quer dizer ao compilador, você pode fazer isso copiando a primeira variável para uma segunda que não tenha '&' e nunca mais use a primeira.
Greggo 30/05

1
@greggo: Dizer que, se baré uma registervariável, um compilador poderá, a seu lazer substituir foo(&bar);com int temp=bar; foo(&temp); bar=temp;, mas tendo o endereço barseria proibido na maioria dos outros contextos não parecer uma regra muito complicada. Se a variável pudesse ser mantida em um registro, a substituição tornaria o código menor. Se a variável precisaria ser mantida na RAM de qualquer maneira, a substituição aumentaria o código. Deixar a questão de fazer a substituição até o compilador levaria a um código melhor nos dois casos.
Supercat

1
@greggo: permitir uma registerqualificação em variáveis ​​globais, independentemente de o compilador permitir ou não o endereço, permitiria ótimas otimizações nos casos em que uma função embutida que usa uma variável global é chamada repetidamente em um loop. Não consigo pensar em outra maneira de deixar essa variável ser mantida em um registro entre iterações de loop - você pode?
Supercat

13

Eu li que ele é usado para otimizar, mas não está claramente definido em nenhum padrão.

De fato, é claramente definido pelo padrão C. Citando o rascunho do N1570, seção 6.7.1, parágrafo 6 (outras versões têm a mesma redação):

Uma declaração de um identificador para um objeto com especificador de classe de armazenamento registersugere que o acesso ao objeto seja o mais rápido possível. A extensão em que essas sugestões são eficazes é definida pela implementação.

O &operador unário não pode ser aplicado a um objeto definido com registere registernão pode ser usado em uma declaração externa.

Existem algumas outras regras (bastante obscuras) que são específicas para registerobjetos qualificados:

  • Definir um objeto de matriz com registercomportamento indefinido.
    Correção: É legal definir um objeto de matriz register, mas você não pode fazer nada útil com esse objeto (a indexação em uma matriz requer o endereço do seu elemento inicial).
  • O _Alignasespecificador (novo em C11) não pode ser aplicado a esse objeto.
  • Se o nome do parâmetro passado para a va_startmacro for registerqualificado, o comportamento será indefinido.

Pode haver alguns outros; faça o download de um rascunho do padrão e pesquise "registrar" se estiver interessado.

Como o nome indica, o significado original de registerera exigir que um objeto fosse armazenado em um registro da CPU. Mas com as melhorias na otimização de compiladores, isso se tornou menos útil. As versões modernas do padrão C não se referem aos registradores da CPU, porque não precisam mais (precisam) assumir que existe isso (existem arquiteturas que não usam registradores). O senso comum é que a aplicação registera uma declaração de objeto tem mais probabilidade de agravar o código gerado, porque interfere na alocação de registro do próprio compilador. Ainda pode haver alguns casos em que isso é útil (por exemplo, se você realmente sabe quantas vezes uma variável será acessada e seu conhecimento é melhor do que o que um compilador de otimização moderno pode descobrir).

O principal efeito tangível de registeré que ele impede qualquer tentativa de obter o endereço de um objeto. Isso não é particularmente útil como uma dica de otimização, uma vez que pode ser aplicada apenas a variáveis ​​locais, e um compilador de otimização pode ver por si mesmo que o endereço de um objeto desse tipo não foi utilizado.


então o comportamento deste programa é realmente indefinido de acordo com o padrão C? Está bem definido em C ++? Eu acho que está bem definido em C ++.
destructor

@ Destructor: Por que seria indefinido? Não há nenhum registerobjeto de matriz qualificado, se é isso que você está pensando.
21416 Keith Thompson

Ah, desculpe, esqueci de escrever a palavra-chave register na declaração da matriz em main (). Está bem definido em C ++?
Destructor

Eu estava errado em definir registerobjetos de matriz; veja o primeiro marcador atualizado na minha resposta. É legal definir esse objeto, mas você não pode fazer nada com ele. Se você adicionar registerà definição de sem seu exemplo , o programa é ilegal (uma violação de restrição) em C. O C ++ não impõe as mesmas restrições register; portanto, o programa seria C ++ válido (mas o uso registerseria inútil).
21116 Keith Thompson

@KeithThompson: a registerpalavra-chave poderia servir a um propósito útil se fosse legal tomar o endereço de uma variável, mas apenas nos casos em que a semântica não fosse afetada, copiando a variável para um temporário quando seu endereço for obtido e recarregando-o do temporário no próximo ponto de sequência. Isso permitiria aos compiladores assumir que a variável pode ser mantida com segurança em um registro em todos os acessos de ponteiro, desde que seja liberada em qualquer local em que seu endereço seja utilizado.
Supercat

9

Hora da história!

C, como linguagem, é uma abstração de um computador. Ele permite que você faça coisas, em termos do que um computador faz, que é manipular memória, fazer contas, imprimir coisas etc.

Mas C é apenas uma abstração. E, finalmente, o que está extraindo de você é a linguagem Assembly. Assembly é o idioma que uma CPU lê e, se você a usa, faz as coisas em termos de CPU. O que uma CPU faz? Basicamente, ele lê da memória, faz contas e grava na memória. A CPU não faz apenas matemática em números na memória. Primeiro, você precisa mover um número de memória para memória dentro da CPU, chamada de registrador. Quando terminar de fazer o que for necessário para esse número, você poderá voltar à memória normal do sistema. Por que usar a memória do sistema? Os registros são limitados em número. Você obtém apenas cem bytes nos processadores modernos, e os processadores populares mais antigos eram ainda mais fantasticamente limitados (o 6502 tinha 3 registros de 8 bits para seu uso gratuito). Portanto, sua operação matemática média se parece com:

load first number from memory
load second number from memory
add the two
store answer into memory

Muito disso é ... não é matemática. Essas operações de carregamento e armazenamento podem levar até metade do tempo de processamento. C, sendo uma abstração de computadores, liberou o programador da preocupação de usar e manipular registros, e como o número e o tipo variam entre computadores, C coloca a responsabilidade da alocação de registros apenas no compilador. Com uma exceção.

Quando você declara uma variável register, você está dizendo ao compilador "Sim, pretendo que essa variável seja muito usada e / ou tenha vida curta. Se eu fosse você, tentaria mantê-la em um registro". Quando o padrão C diz que os compiladores não precisam fazer nada, isso ocorre porque o padrão C não sabe para qual computador você está compilando, e pode ser como o 6502 acima, onde todos os três registros são necessários apenas para operar , e não há registro sobressalente para manter seu número. No entanto, quando diz que você não pode usar o endereço, é porque os registros não têm endereços. Eles são as mãos do processador. Como o compilador não precisa fornecer um endereço e, como nunca pode ter um endereço, várias otimizações estão abertas para o compilador. Pode, por exemplo, manter o número sempre registrado. Não não precisa se preocupar com o local onde está armazenado na memória do computador (além da necessidade de recuperá-lo novamente). Poderia até colocá-lo em outra variável, entregá-lo a outro processador, mudar de local etc.

tl; dr: Variáveis ​​de vida curta que fazem muita matemática. Não declare muitos de uma vez.


5

Você está mexendo no sofisticado algoritmo de coloração gráfica do compilador. Isso é usado para alocação de registro. Bem, principalmente. Ele atua como uma dica para o compilador - isso é verdade. Mas não é totalmente ignorado, pois você não tem permissão para usar o endereço de uma variável de registro (lembre-se de que o compilador, agora à sua disposição, tentará agir de maneira diferente). De certa forma, está dizendo para você não usá-lo.

A palavra-chave foi usada há muito, muito tempo. Quando havia tão poucos registros que podiam contar todos eles usando o dedo indicador.

Mas, como eu disse, preterido não significa que você não possa usá-lo.


13
Alguns dos hardwares mais antigos tinham mais registros do que as modernas máquinas Intel. A contagem de registros não tem nada a ver com a idade e tudo a ver com a arquitetura da CPU.
APENAS MINHA OPINIÃO correta

2
@JUSTMYcorrectOPINION De fato, o X86 tem basicamente seis no total, deixando no máximo 1 ou 2 para dedicação ao 'registro'. De fato, uma vez que tanto código foi escrito ou portado para uma máquina com registro ruim, suspeito que isso tenha contribuído muito para a palavra-chave 'register' se tornar um placebo - não adianta sugerir registros quando não há nenhum. Aqui estamos mais de 4 anos depois, e felizmente o x86_64 aumentou para 14, e o ARM também é uma grande coisa agora.
22615 greggo

4

Apenas uma pequena demonstração (sem nenhum objetivo no mundo real) para comparação: ao remover as registerpalavras - chave antes de cada variável, esse trecho de código leva 3,41 segundos no meu i7 (GCC), com register o mesmo código completo em 0,7 segundos.

#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;
}

2
Com o gcc 4.8.4 e -O3, não tenho diferença. Sem iterações -O3 e 40000, recebo talvez 50 ms a menos em um tempo total de 1,5s, mas não o executei o suficiente para saber se isso era estatisticamente significativo.
zstewart

Não há diferença com o CLANG 5.0, a plataforma é AMD64. (Eu verifiquei a saída ASM.)
ern0

4

Testei a palavra-chave register no QNX 6.5.0 usando o seguinte código:

#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;
}

Eu obtive os seguintes resultados:

-> 807679611 ciclos decorridos

-> Este sistema possui 3300830000 ciclos por segundo

-> Os ciclos em segundos são ~ 0,244600

E agora sem registrar int:

int a=0, b = 1, c = 3, i;

Eu tenho:

-> 1421694077 ciclos decorridos

-> Este sistema possui 3300830000 ciclos por segundo

-> Os ciclos em segundos são ~ 0,430700


2

O Register notificaria o compilador que o codificador acreditava que essa variável seria gravada / lida o suficiente para justificar seu armazenamento em um dos poucos registros disponíveis para uso variável. A leitura / gravação de registros geralmente é mais rápida e pode exigir um conjunto menor de código operacional.

Atualmente, isso não é muito útil, pois a maioria dos otimizadores dos compiladores é melhor que você para determinar se um registro deve ser usado para essa variável e por quanto tempo.


2

Durante os anos setenta, no início da linguagem C, a palavra-chave register foi introduzida para permitir ao programador dar dicas ao compilador, informando que a variável seria usada com muita frequência e que seria prudente mantenha seu valor em um dos registros internos do processador.

Atualmente, os otimizadores são muito mais eficientes do que os programadores para determinar variáveis ​​com maior probabilidade de serem mantidas em registros, e o otimizador nem sempre leva em conta a dica do programador.

Muitas pessoas recomendam erroneamente não usar a palavra-chave register.

Vamos ver o porquê!

A palavra-chave register tem um efeito colateral associado: você não pode fazer referência (obter o endereço de) uma variável do tipo registrador.

As pessoas que aconselham outras pessoas a não usar registros tomam isso erroneamente como um argumento adicional.

No entanto, o simples fato de saber que você não pode pegar o endereço de uma variável de registro permite que o compilador (e seu otimizador) saiba que o valor dessa variável não pode ser modificado indiretamente por meio de um ponteiro.

Quando, em um determinado ponto do fluxo de instruções, uma variável de registro tem seu valor atribuído no registro de um processador, e o registro não foi usado desde então para obter o valor de outra variável, o compilador sabe que não precisa recarregar o valor da variável nesse registro. Isso permite evitar o acesso caro e inútil à memória.

Faça seus próprios testes e você obterá melhorias significativas de desempenho em seus loops mais internos.

c_register_side_effect_performance_boost


1

Nos compiladores C suportados, ele tenta otimizar o código para que o valor da variável seja mantido em um registro real do processador.


1

O compilador Visual C ++ da Microsoft ignora a registerpalavra - chave quando a otimização global de alocação de registro (o sinalizador do compilador / Oe) está habilitada.

Consulte Registrar palavra - chave no MSDN.


1

A palavra-chave Register diz ao compilador para armazenar a variável específica nos registros da CPU, para que ela possa ser acessada rapidamente. Do ponto de vista de um programador, a palavra-chave register é usada para as variáveis ​​que são muito usadas em um programa, para que o compilador possa acelerar o código. Embora dependa do compilador manter a variável nos registros da CPU ou na memória principal.


0

Register indica ao compilador para otimizar esse código, armazenando essa variável específica nos registradores e na memória. é uma solicitação ao compilador, o compilador pode ou não considerar essa solicitação. Você pode usar esse recurso no caso de algumas de suas variáveis ​​serem acessadas com muita frequência. Por exemplo: um loop.

Mais uma coisa é que, se você declarar uma variável como registradora, não poderá obter seu endereço, pois ela não será armazenada na memória. obtém sua alocação no registro da CPU.


0

saída do gcc 9.3 asm, sem usar sinalizadores de otimização (tudo nesta resposta se refere à compilação padrão sem sinalizadores de otimização):

#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

Isso força ebxa ser usado para o cálculo, o que significa que ele precisa ser empurrado para a pilha e restaurado no final da função porque é salvo no chamado. registerproduz mais linhas de código e 1 gravação na memória e 1 leitura na memória (embora realisticamente, isso poderia ter sido otimizado para 0 R / Ws se o cálculo tivesse sido feito esi, o que acontece com os C ++ const register). Não usar registercausa 2 gravações e 1 leitura (embora o armazenamento para carregar o encaminhamento ocorra na leitura). Isso ocorre porque o valor precisa estar presente e atualizado diretamente na pilha para que o valor correto possa ser lido pelo endereço (ponteiro). registernão possui esse requisito e não pode ser apontado. conste registersão basicamente o oposto volatilee usandovolatilesubstituirá as otimizações const no escopo de arquivo e bloco e as registerotimizações no escopo de bloco. const registere registerproduzirá saídas idênticas porque const não faz nada em C no escopo do bloco, portanto, apenas as registerotimizações se aplicam.

No clang, registeré ignorado, mas as constotimizações ainda ocorrem.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.