Quando a palavra-chave register é realmente útil em C?


10

Estou confuso sobre o uso da registerpalavra-chave em C. É geralmente dito que seu uso não é necessário, como nesta pergunta no stackoverflow .

Essa palavra-chave é totalmente redundante em C devido a compiladores modernos ou há situações em que ainda pode ser útil? Se sim, quais são as situações em que o uso da registerpalavra-chave é realmente útil?


4
Acho que a pergunta vinculada e as respostas são as mesmas que você pode esperar aqui. Portanto, não haverá novas informações que você possa obter aqui.
Uwe Plonus

@UwePlonus Eu pensei o mesmo sobre constpalavras-chave, mas essa pergunta provou que eu estava errado. Então, vou esperar e ver o que recebo.
Aseem Bansal

Eu acho que a constpalavra-chave é algo diferente contra o registro.
Uwe Plonus

4
É útil se você acidentalmente voltar no tempo e for forçado a usar um dos primeiros compiladores C. Fora isso, não é de todo útil, tem sido completamente obsoleta há anos.
31913 JohnB

@UwePlonus Eu apenas quis dizer que pode haver cenários desconhecidos para mim nos quais uma palavra-chave possa ser útil.
Aseem Bansal

Respostas:


11

Não é redundante em termos de linguagem, é apenas que, ao usá-lo, você está dizendo ao compilador que você "preferiria" ter uma variável armazenada no registro. No entanto, não há absolutamente nenhuma garantia de que isso realmente ocorra durante o tempo de execução.


9
Mais do que isso, é quase sempre o caso que o compilador sabe melhor e você está desperdiçando sua respiração
Daniel Gratzer

6
@jozefg: ainda pior. Você corre o risco de o compilador honrar sua solicitação / dica e produzir um código pior como resultado.
Bart van Ingen Schenau

9

Como já mencionado, os otimizadores de compilador essencialmente tornam a registerpalavra - chave obsoleta para outros fins que não a prevenção de alias. No entanto, existem bases de código inteiras que são compiladas com a otimização desativada ( -O0no gcc-speak ). Para esse código, a registerpalavra - chave pode ter um grande efeito. Especificamente, variáveis ​​que de outra forma obteriam um slot na pilha (ou seja, todos os parâmetros de função e variáveis ​​automáticas) podem ser colocadas diretamente em um registro se declaradas com a registerpalavra - chave.

Aqui está um exemplo do mundo real: suponha que alguma recuperação do banco de dados tenha ocorrido e que o código de recuperação tenha inserido a tupla recuperada em uma estrutura C. Além disso, suponha que algum subconjunto dessa estrutura C precise ser copiado para outra estrutura - talvez essa segunda estrutura seja um registro de cache que represente os metadados armazenados no banco de dados que, devido a restrições de memória, apenas armazene em cache um subconjunto de cada registro de metadados como armazenado no banco de dados.

Dada uma função que leva um ponteiro para cada tipo de estrutura e cujo único trabalho é copiar alguns membros da estrutura inicial para a segunda estrutura: as variáveis ​​do ponteiro de estrutura permanecerão na pilha. Como as atribuições ocorrem dos membros de uma estrutura para a outra, os endereços da estrutura serão carregados, para cada atribuição, em um registro para executar o acesso dos membros da estrutura que estão sendo copiados. Se os ponteiros de estrutura fossem declarados com a registerpalavra - chave, os endereços das estruturas permaneceriam nos registros, cortando efetivamente as instruções de carregamento de endereço-em-registro para cada atribuição.

Novamente, lembre-se de que a descrição acima se aplica ao código não otimizado .


6

Você basicamente diz ao compilador que não aceitará o endereço da variável e o compilador poderá ostensivamente fazer otimizações adicionais. Até onde eu sei, os compiladores modernos são capazes de determinar se uma variável pode / deve ser mantida em um registro ou não.

Exemplo:

int main(){
        int* ptr;
        int a;
        register int b;
        ptr = &a;
        ptr = &b; //this won't compile
        return 0;
} 

Dereferência ou pegue o endereço de?
detly

@detly: você é, claro, correto
Lucas

0

Nos dias de 16 bits do computador, muitas vezes era necessário vários registros para executar multiplicações e divisões de 32 bits. À medida que as unidades de ponto flutuante foram incorporadas aos chips e as arquiteturas de 64 bits assumiram o controle, a largura dos registros e o número deles foram expandidos. Isso eventualmente leva a uma completa re-arquitetura da CPU. Consulte Registrar arquivos na Wikipedia.

Em resumo, levaria um pouco de tempo para descobrir o que realmente está acontecendo se você estiver em um chip X86 ou ARM de 64 bits. Se você estiver em uma CPU incorporada de 16 bits, isso pode realmente lhe dar alguma coisa. No entanto, a maioria dos pequenos chips incorporados não executa nada de crítico - o forno de microondas pode estar amostrando o touchpad 10.000 vezes por segundo - nada que sobrecarregue uma CPU de 4 MHz.


1
4 MIPS / 10.000 pesquisas / s = 400 instruções / pesquisa. Essa margem não é quase a mesma que você gostaria de ter. Observe também que alguns processadores de 4 MHz foram microcodificados internamente, o que significa que eles não estavam nem perto de 1 MIP / MHz.
John R. Strohm

@ JohnR.Strohm - Pode haver situações em que alguém possa justificar descobrir exatamente quantos ciclos de instrução serão necessários, mas geralmente a saída mais barata agora é obter um chip mais rápido e colocar o produto na porta. No exemplo dado, é claro, não é necessário continuar a amostragem em 10.000 se houver um comando - ele pode não retomar a amostragem por um quarto de segundo sem causar danos. Está se tornando cada vez mais difícil descobrir onde a otimização direcionada pelo programador será importante.
Meredith Poor

1
Nem sempre é possível "obter um chip mais rápido e liberar o produto". Considere o processamento de imagens em tempo real. As instruções de 640x480 pixels / quadro x 60 quadros / segundo x N por pixel são adicionadas rapidamente. (A lição do processamento de imagens em tempo real é que você sua sangue sobre seus núcleos de pixel e ignora todo o resto, porque ele é executado uma vez por linha ou uma vez por patch ou uma vez por quadro, em vez de centenas de vezes por linha ou patch ou dezenas ou centenas de milhares de vezes por frame).
John R. Strohm

@ JohnR.Strohm - tomando o exemplo de processamento de imagem em tempo real, eu presumo que o ambiente mínimo seja de 32 bits. Saindo do limite (porque não sei o quão prático é isso) muitos aceleradores gráficos embutidos em chips também podem ser usados ​​para reconhecimento de imagem, portanto, os chips ARM (por exemplo) que possuem mecanismos de renderização integrados podem ter ALUs adicionais utilizáveis para reconhecimento. Nesse momento, o uso da palavra-chave 'register' para otimização é uma pequena parte do problema.
Meredith Poor

-3

Para estabelecer se a palavra-chave register tem algum significado, pequenos exemplos de códigos não serão suficientes. Aqui está um código c que me sugere que a palavra-chave register ainda tem um significado. Mas pode ser diferente com o GCC no Linux, eu não sei. O registro int k & l será armazenado em um registro da CPU ou não? Os usuários do Linux (especialmente) devem compilar com o GCC e a otimização. Com o Borland bcc32, a palavra-chave register parece funcionar (neste exemplo), pois o & -operator fornece códigos de erro para os números inteiros declarados no registro. NOTA! Este não é o caso de um pequeno exemplo com o Borland no Windows! Para realmente ver o que o compilador otimiza ou não, ele deve ser mais do que um pequeno exemplo. Loops vazios não servem! No entanto - se um endereço puder ser lido com o operador &, a variável não será armazenada em um registro da CPU. Mas se uma variável declarada de registro não puder ser lida (causando código de erro na compilação) - eu tenho que presumir que a palavra-chave register realmente coloca a variável em um registro de CPU. Pode ser diferente em várias plataformas, eu não sei. (Se funcionar, o número de "ticks" será muito menor com a declaração do registro.

/* reg_or_not.c */  

#include <stdio.h>
#include <time.h>
#include <stdlib> //not requiered for Linux
#define LAPSb 50
#define LAPS 50000
#define MAXb 50
#define MAX 50000


int main (void)
{
/* 20 ints and 2 register ints */   

register int k,l;
int a,aa,b,bb,c,cc,d,dd,e,ee,f,ff,g,gg,h,hh,i,ii,j,jj;


/* measure some ticks also */  

clock_t start_1,start_2; 
clock_t finish_1,finish_2;
long tmp; //just for the workload 


/* pointer declarations of all ints */

int *ap, *aap, *bp, *bbp, *cp, *ccp, *dp, *ddp, *ep, *eep;
int *fp, *ffp, *gp, *ggp, *hp, *hhp, *ip, *iip, *jp, *jjp;
int *kp,*lp;

/* end of declarations */
/* read memory addresses, if possible - which can't be done in a CPU-register */     

ap=&a; aap=&aa; bp=&b; bbp=&bb;
cp=&c; ccp=&cc; dp=&d; ddp=&dd;
ep=&e; eep=&ee; fp=&f; ffp=&ff;
gp=&g; ggp=&gg; hp=&h; hhp=&hh;
ip=&i; iip=&ii; jp=&j; jjp=&jj;

//kp=&k;  //won't compile if k is stored in a CPU register  
//lp=&l;  //same - but try both ways !


/* what address , isn't the issue in this case - but if stored in memory    some "crazy" number will be shown, whilst CPU-registers can't be read */

printf("Address a aa: %u     %u\n",a,aa);
printf("Address b bb: %u     %u\n",b,bb);
printf("Address c cc: %u     %u\n",c,cc);
printf("Address d dd: %u     %u\n",d,dd);
printf("Address e ee: %u     %u\n",e,ee);
printf("Address f ff: %u     %u\n",f,ff);
printf("Address g gg: %u     %u\n",g,gg);
printf("Address h hh: %u     %u\n",h,hh);
printf("Address i ii: %u     %u\n",i,ii);
printf("Address j jj: %u     %u\n\n",j,jj);

//printf("Address k:  %u \n",k); //no reason to try "k" actually is in a CPU-register 
//printf("Address l:  %u \n",l); 


start_2=clock(); //just for fun      

/* to ensure workload */
for (a=1;a<LAPSb;a++) {for (aa=0;aa<MAXb;aa++);{tmp+=aa/a;}}
for (b=1;b<LAPSb;b++) {for (bb=0;bb<MAXb;bb++);{tmp+=aa/a;}}
for (a=1;c<LAPSb;c++) {for (cc=0;cc<MAXb;cc++);{tmp+=bb/b;}}
for (d=1;d<LAPSb;d++) {for (dd=0;dd<MAXb;dd++);{tmp+=cc/c;}}
for (e=1;e<LAPSb;e++) {for (ee=0;ee<MAXb;ee++);{tmp+=dd/d;}}
for (f=1;f<LAPSb;f++) {for (ff=0;ff<MAXb;ff++);{tmp+=ee/e;}}
for (g=1;g<LAPSb;g++) {for (gg=0;gg<MAXb;gg++);{tmp+=ff/f;}}
for (h=1;h<LAPSb;h++) {for (hh=0;hh<MAXb;hh++);{tmp+=hh/h;}}
for (jj=1;jj<LAPSb;jj++) {for (ii=0;ii<MAXb;ii++);{tmp+=ii/jj;}}

start_1=clock(); //see following printf
for (i=0;i<LAPS;i++) {for (j=0;j<MAX;j++);{tmp+=j/i;}} /* same double   loop - in supposed memory */
finish_1=clock(); //see following printf

printf ("Memory: %ld ticks\n\n", finish_1 - start_1); //ticks for memory

start_1=clock(); //see following printf
for (k=0;k<LAPS;k++) {for (l=0;l<MAX;l++);{tmp+=l/k;}}  /* same double       loop - in supposed register*/
finish_1=clock(); //see following printf     

printf ("Register: %ld ticks\n\n", finish_1 - start_1); //ticks for CPU register (?) any difference ?   

finish_2=clock();

printf ("Total: %ld ticks\n\n", finish_2 - start_2); //really for fun only           

system("PAUSE"); //only requiered for Windows, so the CMD-window doesn't vanish     

return 0;

} 

Haverá uma divisão por zero acima, por favor alteração {tmp + = ii / mf;} para {tmp + = jj / ii;} - realmente sinto muito por isso
John P Eriksson

Também deixe k e eu começarmos com 1 - não zero. Sinto muito.
John P Eriksson

3
Você pode editar sua resposta em vez de escrever correções nos comentários.
Jan Doggen
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.