Quando la parola chiave register è effettivamente utile in C?


10

Sono confuso sull'uso della registerparola chiave in C. Si dice generalmente che il suo uso non è necessario come in questa domanda su StackOverflow .

Questa parola chiave è totalmente ridondante in C a causa dei compilatori moderni o ci sono situazioni in cui può ancora essere utile? In caso affermativo, quali sono alcune situazioni in cui l'uso della registerparola chiave è effettivamente utile?


4
Penso che la domanda collegata e le risposte ad essa siano le stesse che ci si può aspettare qui. Quindi non ci saranno nuove informazioni che puoi ottenere qui.
Uwe Plonus,

@UwePlonus Ho pensato lo stesso della constparola chiave, ma questa domanda ha dimostrato che mi sbagliavo. Quindi aspetterò e vedrò cosa ottengo.
Aseem Bansal,

Penso che la constparola chiave sia qualcosa di diverso rispetto al registro.
Uwe Plonus,

4
È utile se torni indietro nel tempo e sei costretto a usare uno dei primi compilatori C. A parte questo non è affatto utile, è stato completamente obsoleto per anni.
John B

@UwePlonus Volevo solo dire che potrebbero esserci scenari a me sconosciuti in cui una parola chiave potrebbe essere utile.
Aseem Bansal,

Risposte:


11

Non è ridondante in termini di linguaggio, è solo che utilizzandolo, stai dicendo al compilatore, "preferiresti" avere una variabile memorizzata nel registro. Vi è tuttavia assolutamente zero garanzia che ciò accada effettivamente durante il runtime.


9
Inoltre, quasi sempre il compilatore lo sa meglio e stai sprecando fiato
Daniel Gratzer,

6
@jozefg: anche peggio. Corri il rischio che il compilatore onori la tua richiesta / suggerimento e di conseguenza produca codice peggiore .
Bart van Ingen Schenau,

9

Come già accennato, gli ottimizzatori del compilatore rendono essenzialmente la registerparola chiave obsoleta per scopi diversi dalla prevenzione dell'aliasing. Tuttavia, ci sono intere basi di codice che vengono compilate con l'ottimizzazione disattivata ( -O0in gcc-speak ). Per tale codice, la registerparola chiave può avere un grande effetto. In particolare, le variabili che altrimenti avrebbero uno slot nello stack (cioè tutti i parametri di funzione e le variabili automatiche) possono essere inserite direttamente in un registro se dichiarate con la registerparola chiave.

Ecco un esempio reale: supponiamo che si sia verificato un recupero del database e che il codice di recupero abbia inserito la tupla recuperata in una struttura C. Inoltre, supponiamo che alcuni sottogruppi di questa struttura C debbano essere copiati in un'altra struttura, forse questa seconda struttura è un record di cache che rappresenta i metadati memorizzati nel database che, a causa di vincoli di memoria, memorizzano nella cache solo un sottoinsieme di ciascun record di metadati come archiviato nel database.

Data una funzione che prende un puntatore a ciascun tipo di struttura e il cui unico lavoro è copiare alcuni membri dalla struttura iniziale alla seconda struttura: le variabili del puntatore della struttura vivranno nello stack. Man mano che le assegnazioni avvengono dai membri di una struttura all'altra, gli indirizzi della struttura verranno caricati in un registro per eseguire l'accesso dei membri della struttura che vengono copiati. Se i puntatori struct fossero dichiarati con la registerparola chiave, gli indirizzi delle strutture rimarrebbero nei registri, tagliando efficacemente le istruzioni di caricamento dell'indirizzo nel registro per ogni assegnazione.

Ancora una volta, ricorda che la descrizione sopra si applica al codice non ottimizzato .


6

Fondamentalmente dici al compilatore che non prenderai l'indirizzo della variabile e il compilatore può quindi fare apparentemente ulteriori ottimizzazioni. Per quanto ne so, i compilatori moderni sono abbastanza in grado di determinare se una variabile può / debba essere tenuta in un registro o meno.

Esempio:

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

Dereference o prendere l'indirizzo di?
detly

@detly: ovviamente hai ragione
Lucas,

0

Nei giorni di computer a 16 bit, per eseguire moltiplicazioni e divisioni a 32 bit erano spesso necessari più registri. Man mano che unità a virgola mobile venivano incorporate nei chip e quindi le architetture a 64 bit "prendevano il controllo", sia la larghezza dei registri che il numero di esse si espandevano. Questo alla fine porta a una completa riprogettazione della CPU. Vedi Registra file su Wikipedia.

In breve, ci vorrebbe un po 'di tempo per capire cosa sta realmente succedendo se si utilizza un chip X86 o ARM a 64 bit. Se utilizzi una CPU integrata a 16 bit, questo potrebbe effettivamente darti qualcosa. Tuttavia, la maggior parte dei chip integrati di piccole dimensioni non esegue nulla di critico in termini di tempo - il forno a microonde potrebbe campionare il touchpad 10.000 volte al secondo - niente che tenda una CPU da 4 Mhz.


1
4 MIPS / 10.000 sondaggi / sec = 400 istruzioni / sondaggio. Non è il margine che vorresti avere. Si noti inoltre che alcuni processori a 4 MHz erano microcodificati internamente, il che significa che non erano in nessun posto vicino a 1 MIP / MHz.
John R. Strohm,

@ JohnR.Strohm - Potrebbero esserci situazioni in cui si potrebbe giustificare capire esattamente quanti cicli di istruzioni ci vorranno, ma spesso la via più economica ora è semplicemente ottenere un chip più veloce e portare il prodotto fuori dalla porta. Nell'esempio fornito, ovviamente, non è necessario continuare a campionare a 10.000 se si dispone di un comando: potrebbe non riprendere il campionamento per un quarto di secondo senza che si verifichino danni. Sta diventando sempre più difficile capire dove sarà importante l'ottimizzazione diretta dal programmatore.
Meredith Poor,

1
Non è sempre possibile "ottenere un chip più veloce e portare il prodotto fuori dalla porta". Prendi in considerazione l'elaborazione delle immagini in tempo reale. 640x480 pixel / frame x 60 frame / secondo x N istruzioni per pixel si aggiungono rapidamente. (La lezione dall'elaborazione delle immagini in tempo reale è che si suda il sangue sui kernel pixel e si ignora quasi tutto il resto, perché viene eseguito una volta per riga o una volta per patch o una volta per frame, rispetto a centinaia di volte per riga o patch o decine o centinaia di migliaia di volte per frame.)
John R. Strohm

@ JohnR.Strohm - prendendo l'esempio di elaborazione delle immagini in tempo reale, presumo che l'ambiente minimo sia di 32 bit. Andando su un arto (perché non so quanto sia pratico fare) molti acceleratori grafici integrati nei chip potrebbero anche essere utilizzabili per il riconoscimento delle immagini, quindi i chip ARM (ad esempio) che hanno motori di rendering integrati potrebbero avere ALU aggiuntive utilizzabili per il riconoscimento. A quel punto l'uso della parola chiave 'register' per l'ottimizzazione è una piccola parte del problema.
Meredith Poor,

-3

Al fine di stabilire se la parola chiave register ha qualche significato, i piccoli codici di esempio non lo faranno. Ecco un codice c che mi suggerisce che la parola chiave register abbia ancora un significato. Ma potrebbe essere diverso con GCC su Linux, non lo so. Il registro int k & l verrà memorizzato in un registro CPU o no? Gli utenti Linux (in particolare) dovrebbero compilare con GCC e ottimizzazione. Con Borland bcc32 la parola chiave register sembra funzionare (in questo esempio), poiché l'operatore & fornisce i codici di errore per i numeri interi dichiarati dal registro. NOTA! Questo NON è il caso di un piccolo esempio con Borland su Windows! Per vedere davvero ciò che il compilatore ottimizza o meno, deve essere un esempio più che minuscolo. I loop vuoti non lo faranno! Tuttavia - SE un indirizzo PU CAN essere letto con l'operatore &, la variabile non è memorizzata in un registro CPU. Ma se una variabile dichiarata dal registro non può essere letta (causando un codice di errore durante la compilazione) - Devo presumere che la parola chiave register effettivamente inserisca la variabile in un registro CPU. Potrebbe differire su varie piattaforme, non lo so. (Se funziona, il numero di "tick" sarà molto più basso con la dichiarazione del 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;

} 

Ci sarà una divisione con zero sopra, per favore cambia {tmp + = ii / jj;} in {tmp + = jj / ii;} - mi dispiace davvero per questo
John P Eriksson

Inoltre, lascia che k ed io iniziamo con 1 - non zero. Molto dispiaciuto.
John P Eriksson,

3
Puoi modificare la tua risposta invece di scrivere correzioni nei commenti.
Jan Doggen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.