Cosa fa la register
parola chiave in linguaggio C? Ho letto che viene utilizzato per l'ottimizzazione ma non è chiaramente definito in nessuno standard. È ancora pertinente e, in tal caso, quando lo useresti?
register
variabile.
Cosa fa la register
parola chiave in linguaggio C? Ho letto che viene utilizzato per l'ottimizzazione ma non è chiaramente definito in nessuno standard. È ancora pertinente e, in tal caso, quando lo useresti?
register
variabile.
Risposte:
È un suggerimento per il compilatore che la variabile verrà utilizzata pesantemente e che, se possibile, si consiglia di conservarla in un registro del processore.
Molti compilatori moderni lo fanno automaticamente e sono più bravi a sceglierli di noi umani.
register
venga preso l'indirizzo di una variabile; questo è l' unico effetto obbligatorio della register
parola chiave. Anche questo è sufficiente per migliorare le ottimizzazioni, perché diventa banale dire che la variabile può essere modificata solo all'interno di questa funzione.
Sono sorpreso che nessuno abbia menzionato che non puoi prendere un indirizzo per la variabile register, anche se il compilatore decide di mantenere la variabile in memoria piuttosto che nel registro.
Quindi usando register
te non vinci nulla (comunque il compilatore deciderà da solo dove posizionare la variabile) e perderà l' &
operatore - nessuna ragione per usarla.
register
è utile per questo anche se il compilatore non lo inserisce in un registro.
const
è anche inutile poiché non vince nulla, perdi solo la possibilità di cambiare una variabile. register
potrebbe essere usato per assicurarsi che nessuno prenda l'indirizzo di una variabile in futuro senza pensare. Non ho mai avuto motivo di usare register
però.
Indica al compilatore di provare a utilizzare un registro CPU, anziché RAM, per memorizzare la variabile. I registri sono nella CPU e sono molto più veloci di accesso rispetto alla RAM. Ma è solo un suggerimento per il compilatore e potrebbe non essere seguito.
So che questa domanda riguarda C, ma la stessa domanda per C ++ è stata chiusa come un duplicato esatto di questa domanda. Pertanto, questa risposta potrebbe non essere valida per C.
L'ultima bozza dello standard C ++ 11, N3485 , dice questo nel 7.1.1 / 3:
Un
register
identificatore è un suggerimento per l'implementazione che la variabile così dichiarata verrà utilizzata pesantemente. [ nota: il suggerimento può essere ignorato e nella maggior parte delle implementazioni verrà ignorato se viene preso l'indirizzo della variabile. Questo uso è sconsigliato ... nota -end ]
In C ++ (ma non in C), lo standard non afferma che non è possibile prendere l'indirizzo di una variabile dichiarata register
; tuttavia, poiché una variabile memorizzata in un registro della CPU per tutta la sua durata non ha una posizione di memoria associata, il tentativo di prendere il suo indirizzo non sarebbe valido e il compilatore ignorerà la register
parola chiave per consentire di prendere l'indirizzo.
Non è rilevante da almeno 15 anni poiché gli ottimizzatori prendono decisioni migliori al riguardo di quanto tu possa fare. Anche quando era rilevante, aveva molto più senso su un'architettura CPU con molti registri, come SPARC o M68000 rispetto a Intel con la sua scarsità di registri, molti dei quali riservati dal compilatore per i propri scopi.
In realtà, register dice al compilatore che la variabile non si alias di nient'altro nel programma (nemmeno di char).
Questo può essere sfruttato dai compilatori moderni in una varietà di situazioni e può aiutare un po 'il compilatore in un codice complesso - in un codice semplice i compilatori possono capirlo da soli.
Altrimenti, non serve a nulla e non viene utilizzato per l'allocazione del registro. Di solito non comporta un peggioramento delle prestazioni per specificarlo, purché il compilatore sia abbastanza moderno.
register
vieta di prendere l'indirizzo, poiché altrimenti potrebbe essere utile far conoscere ai compilatori i casi in cui un compilatore sarebbe in grado di applicare le ottimizzazioni del registro nonostante l'indirizzo di una variabile venga passato a una funzione esterna (la variabile dovrebbe essere scaricato nella memoria per quella particolare chiamata , ma una volta restituita la funzione, il compilatore potrebbe nuovamente trattarla come una variabile il cui indirizzo non era mai stato preso).
bar
è una register
variabile, un compilatore può, a sua svago sostituire foo(&bar);
con int temp=bar; foo(&temp); bar=temp;
, ma prendendo l'indirizzo bar
sarebbe vietato nella maggior parte degli altri contesti non sembrerebbe come una regola troppo complicato. Se la variabile potesse altrimenti essere tenuta in un registro, la sostituzione ridurrebbe il codice. Se la variabile dovesse essere comunque mantenuta nella RAM, la sostituzione renderebbe il codice più grande. Lasciare la questione se effettuare la sostituzione fino al compilatore porterebbe a un codice migliore in entrambi i casi.
register
qualifica per le variabili globali, indipendentemente dal fatto che il compilatore consenta o meno l'indirizzo, consentirebbe alcune ottimizzazioni nei casi in cui una funzione incorporata che utilizza una variabile globale viene chiamata ripetutamente in un ciclo. Non riesco a pensare ad altro modo per lasciare che quella variabile sia tenuta in un registro tra iterazioni cicliche - vero?
Ho letto che viene utilizzato per l'ottimizzazione ma non è chiaramente definito in nessuno standard.
In effetti è chiaramente definito dallo standard C. Citando la bozza N1570 sezione 6.7.1 paragrafo 6 (altre versioni hanno la stessa formulazione):
Una dichiarazione di un identificatore per un oggetto con identificatore della classe di memoria
register
suggerisce che l'accesso all'oggetto sia il più veloce possibile. La misura in cui tali suggerimenti sono efficaci è definita dall'implementazione.
L' &
operatore unario non può essere applicato a un oggetto definito con register
e register
non può essere utilizzato in una dichiarazione esterna.
Ci sono alcune altre regole (abbastanza oscure) che sono specifiche per register
gli oggetti qualificati:
register
ha un comportamento indefinito. register
, ma non è possibile fare nulla di utile con tale oggetto (l'indicizzazione in un array richiede di prendere l'indirizzo del suo elemento iniziale)._Alignas
identificatore (nuovo in C11) non può essere applicato a tale oggetto.va_start
macro è register
-qualificato, il comportamento non è definito.Potrebbero essercene alcuni altri; scarica una bozza dello standard e cerca "registrati" se sei interessato.
Come suggerisce il nome, il significato originale di register
era richiedere la memorizzazione di un oggetto in un registro CPU. Ma con miglioramenti nell'ottimizzazione dei compilatori, questo è diventato meno utile. Le versioni moderne dello standard C non si riferiscono ai registri della CPU, perché non (più devono) presumere che esista una cosa del genere (ci sono architetture che non usano i registri). La saggezza comune è che l'applicazione register
a una dichiarazione di oggetto ha maggiori probabilità di peggiorare il codice generato, poiché interferisce con l'allocazione del registro del compilatore. Potrebbero esserci ancora alcuni casi in cui è utile (ad esempio, se sai davvero con quale frequenza si accederà a una variabile e la tua conoscenza è migliore di ciò che un moderno compilatore ottimizzante può capire).
Il principale effetto tangibile di register
è che impedisce qualsiasi tentativo di prendere l'indirizzo di un oggetto. Questo non è particolarmente utile come suggerimento per l'ottimizzazione, poiché può essere applicato solo alle variabili locali e un compilatore di ottimizzazione può vedere da solo che l'indirizzo di tale oggetto non viene preso.
register
un oggetto array qualificato, se è quello che stai pensando.
register
oggetti array; vedere il primo punto elenco aggiornato nella mia risposta. È legale definire un tale oggetto, ma non puoi farci niente. Se aggiungi register
alla definizione di s
nel tuo esempio , il programma è illegale (una violazione di vincolo) in C. C ++ non pone le stesse restrizioni register
, quindi il programma sarebbe C ++ valido (ma l'utilizzo register
sarebbe inutile).
register
parola chiave potrebbe servire a uno scopo utile se fosse legale prendere l'indirizzo di tale variabile ma solo nei casi in cui la semantica non sarebbe influenzata copiando la variabile in un temporaneo quando viene preso il suo indirizzo e ricaricandolo dal temporaneo al punto successivo della sequenza. Ciò consentirebbe ai compilatori di supporre che la variabile possa essere conservata in modo sicuro in un registro attraverso tutti gli accessi al puntatore, a condizione che sia svuotata in qualsiasi posizione venga preso il suo indirizzo.
Ora della favola!
C, come linguaggio, è un'astrazione di un computer. Ti permette di fare cose, in termini di ciò che fa un computer, cioè manipolare la memoria, fare matematica, stampare cose, ecc.
Ma C è solo un'astrazione. E alla fine, ciò che sta estraendo da te è il linguaggio Assembly. Assembly è il linguaggio che legge una CPU e, se lo usi, fai le cose in termini di CPU. Cosa fa una CPU? Fondamentalmente, legge dalla memoria, fa matematica e scrive nella memoria. La CPU non si limita a fare matematica sui numeri in memoria. Innanzitutto, devi spostare un numero dalla memoria alla memoria all'interno della CPU chiamata registro. Una volta terminato di fare tutto ciò che è necessario fare su questo numero, è possibile spostarlo nuovamente nella normale memoria di sistema. Perché usare la memoria di sistema? I registri sono in numero limitato. Si ottengono solo un centinaio di byte nei moderni processori e i vecchi processori popolari erano ancora più incredibilmente limitati (il 6502 aveva 3 registri a 8 bit per l'uso gratuito). Quindi, l'operazione matematica media è simile a:
load first number from memory
load second number from memory
add the two
store answer into memory
Molto di questo non è ... non matematica. Queste operazioni di caricamento e archiviazione possono richiedere fino alla metà del tempo di elaborazione. C, essendo un'astrazione di computer, liberò il programmatore dalla preoccupazione di usare e destreggiarsi tra i registri, e poiché il numero e il tipo variano tra i computer, C attribuisce la responsabilità dell'allocazione dei registri esclusivamente sul compilatore. Con un'eccezione.
Quando dichiari una variabile register
, stai dicendo al compilatore "Yo, intendo che questa variabile venga usata molto e / o abbia vita breve. Se fossi in te, proverei a tenerlo in un registro." Quando lo standard C dice che i compilatori non devono realmente fare nulla, è perché lo standard C non sa per quale computer stai compilando, e potrebbe essere come il 6502 sopra, dove tutti e 3 i registri sono necessari solo per funzionare e non esiste un registro di riserva per conservare il numero. Tuttavia, quando dice che non puoi prendere l'indirizzo, è perché i registri non hanno indirizzi. Sono le mani del processore. Dal momento che il compilatore non deve fornirti un indirizzo e dal momento che non può mai avere un indirizzo, ora sono disponibili diverse ottimizzazioni per il compilatore. Potrebbe, diciamo, mantenere sempre il numero in un registro. Non non devo preoccuparmi di dove è archiviato nella memoria del computer (oltre alla necessità di recuperarlo di nuovo). Potrebbe anche punirlo in un'altra variabile, darlo a un altro processore, dargli una posizione che cambia, ecc.
tl; dr: variabili di breve durata che fanno molta matematica. Non dichiararne troppi contemporaneamente.
Stai scherzando con il sofisticato algoritmo di colorazione del grafico del compilatore. Questo è usato per l'allocazione del registro. Bene, soprattutto. Funziona come un suggerimento per il compilatore - è vero. Ma non ignorato nella sua interezza poiché non ti è permesso di prendere l'indirizzo di una variabile di registro (ricorda che il compilatore, ora in balia di te, proverà ad agire diversamente). Che in un certo senso ti sta dicendo di non usarlo.
La parola chiave è stata usata molto, molto tempo fa. Quando c'erano solo pochi registri che potevano contarli tutti usando il dito indice.
Ma, come ho detto, deprecato non significa che non puoi usarlo.
Solo una piccola demo (senza alcun scopo nel mondo reale) per il confronto: quando si rimuovono le register
parole chiave prima di ogni variabile, questo pezzo di codice impiega 3,41 secondi sul mio i7 (GCC), con register
lo stesso codice completato in 0,7 secondi.
#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;
}
Ho testato la parola chiave del registro in QNX 6.5.0 utilizzando il seguente codice:
#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;
}
Ho ottenuto i seguenti risultati:
-> 807679611 cicli trascorsi
-> Questo sistema ha 3300830000 cicli al secondo
-> I cicli in secondi sono ~ 0,244600
E ora senza registro int:
int a=0, b = 1, c = 3, i;
Ho ottenuto:
-> 1421694077 cicli scaduti
-> Questo sistema ha 3300830000 cicli al secondo
-> I cicli in secondi sono ~ 0.430700
Register notificherebbe al compilatore che il programmatore riteneva che questa variabile sarebbe stata scritta / letta abbastanza da giustificare la sua memorizzazione in uno dei pochi registri disponibili per l'uso variabile. La lettura / scrittura dai registri è in genere più rapida e può richiedere un set di codici operativi più piccolo.
Al giorno d'oggi, questo non è molto utile, poiché la maggior parte degli ottimizzatori dei compilatori sono migliori di te nel determinare se un registro debba essere usato per quella variabile e per quanto tempo.
Durante gli anni settanta, all'inizio del linguaggio C, è stata introdotta la parola chiave register per consentire al programmatore di dare suggerimenti al compilatore, dicendo che la variabile sarebbe stata usata molto spesso e che sarebbe saggio mantenere il valore in uno dei registri interni del processore.
Al giorno d'oggi, gli ottimizzatori sono molto più efficienti dei programmatori per determinare le variabili che hanno maggiori probabilità di essere tenuti nei registri e l'ottimizzatore non tiene sempre conto del suggerimento del programmatore.
Così tante persone a torto raccomandano di non usare la parola chiave register.
Vediamo perché!
La parola chiave register ha un effetto collaterale associato: non puoi fare riferimento (ottenere l'indirizzo di) una variabile del tipo di registro.
Le persone che consigliano agli altri di non usare i registri prendono erroneamente questo come ulteriore argomento.
Tuttavia, il semplice fatto di sapere che non è possibile prendere l'indirizzo di una variabile di registro, consente al compilatore (e al suo ottimizzatore) di sapere che il valore di questa variabile non può essere modificato indirettamente tramite un puntatore.
Quando a un certo punto del flusso di istruzioni, una variabile di registro ha il suo valore assegnato in un registro del processore e il registro non è stato utilizzato da quando si ottiene il valore di un'altra variabile, il compilatore sa che non è necessario ricaricare il valore della variabile in quel registro. Ciò consente di evitare costosi accessi inutili alla memoria.
Fai i tuoi test e otterrai significativi miglioramenti delle prestazioni nei tuoi loop più interni.
Il compilatore Visual C ++ di Microsoft ignora il file register
parola chiave quando è abilitata l'ottimizzazione globale dell'allocazione dei registri (il flag del compilatore / Oe).
Vedi registro Parola chiave su MSDN.
La parola chiave Register indica al compilatore di memorizzare la particolare variabile nei registri della CPU in modo che possa essere accessibile rapidamente. Dal punto di vista di un programmatore, la parola chiave register viene utilizzata per le variabili che sono fortemente utilizzate in un programma, in modo che il compilatore possa accelerare il codice. Anche se dipende dal compilatore se mantenere la variabile nei registri della CPU o nella memoria principale.
Register indica al compilatore di ottimizzare questo codice memorizzando quella particolare variabile nei registri quindi in memoria. è una richiesta al compilatore, il compilatore può o meno prendere in considerazione questa richiesta. È possibile utilizzare questa funzione nel caso in cui alcune delle variabili siano accessibili molto frequentemente. Ad esempio: un loop.
Un'altra cosa è che se si dichiara una variabile come registro, non è possibile ottenere il suo indirizzo in quanto non è memorizzato in memoria. ottiene la sua allocazione nel registro della CPU.
gcc 9.3 asm output, senza usare flag di ottimizzazione (tutto in questa risposta si riferisce alla compilazione standard senza flag di ottimizzazione):
#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
Questo costringe ebx
a essere utilizzato per il calcolo, il che significa che deve essere spinto nello stack e ripristinato alla fine della funzione perché viene salvato. register
produce più righe di codice e 1 scrittura in memoria e 1 lettura in memoria (anche se realisticamente, questo avrebbe potuto essere ottimizzato su 0 R / W se il calcolo fosse stato eseguito esi
, che è ciò che accade usando i C ++ const register
). Il mancato utilizzo register
causa 2 scritture e 1 lettura (anche se sulla lettura si verificherà l'inoltro da carico a archivio). Questo perché il valore deve essere presente e aggiornato direttamente nello stack in modo che il valore corretto possa essere letto dall'indirizzo (puntatore). register
non ha questo requisito e non può essere indicato. const
e sovrascriverà le ottimizzazioni const a livello di file e blocco e diregister
sono sostanzialmente l'opposto volatile
e l'utilizzo divolatile
register
ottimizzazioni a livello di blocco. const register
e register
produrrà output identici perché const non fa nulla su C in blocco-scope, quindi solo ilregister
applicano ottimizzazioni.
Su clang, register
viene ignorato ma const
si verificano ancora ottimizzazioni.