Registra la parola chiave in C ++


89

Qual è la differenza tra

int x=7;

e

register int x=7;

?

Sto usando C ++.


8
@GMan: ANSI C non consente di prendere l'indirizzo di un oggetto registro; questa restrizione non si applica a C ++
Brian R. Bondy

1
@ Brian: Hm, hai ragione. È solo in una nota ora (che probabilmente verrà ignorato se l'indirizzo viene preso), ma non obbligatorio. Buono a sapersi. (Beh, una specie di.: P)
GManNickG

8
Il voto per la riapertura registerha una semantica diversa tra C e C ++.
CB Bailey

3
come conseguenza di ciò, in C è possibile proibire la conversione da array a puntatore facendo un registro di array: register int a[1];con quella dichiarazione, non è possibile indicizzare quell'array. Se ci provi, fai UB
Johannes Schaub - litb

2
Anzi, ho votato per riaprire. Ho votato per la chiusura prima di sapere che c'era una differenza.
GManNickG

Risposte:


24

In C ++ come esisteva nel 2010, qualsiasi programma valido che utilizza le parole chiave "auto" o "register" sarà semanticamente identico a uno con quelle parole chiave rimosse (a meno che non compaiano in macro in stringa o altri contesti simili). In questo senso le parole chiave sono inutili per la corretta compilazione dei programmi. D'altra parte, le parole chiave potrebbero essere utili in determinati contesti macro per garantire che l'uso improprio di una macro causi un errore in fase di compilazione piuttosto che produrre codice fasullo.

In C ++ 11 e nelle versioni successive del linguaggio, la autoparola chiave è stata riproposta per agire come uno pseudo-tipo per gli oggetti inizializzati, che un compilatore sostituirà automaticamente con il tipo di espressione di inizializzazione. Pertanto, in C ++ 03, la dichiarazione: auto int i=(unsigned char)5;era equivalente a int i=5;quando utilizzata all'interno di un contesto di blocco ed auto i=(unsigned char)5;era una violazione dei vincoli. In C ++ 11, è auto int i=(unsigned char)5;diventato una violazione dei vincoli mentre è auto i=(unsigned char)5;diventato equivalente a auto unsigned char i=5;.


22
Un esempio dell'ultimo bit può essere utile.
Dennis Zickefoose

14
Questa risposta non è più corretta, dal 2011 la parola chiave autonon può essere semplicemente omessa ... Forse potresti aggiornare la tua risposta.
Walter

2
@ Walter: puoi citare cosa è cambiato? Non ho seguito tutti i cambi di lingua.
supercat

2
@supercat, sì, per il momento, ma registerè deprecato e ci sarà una proposta per rimuoverlo per C ++ 17.
Jonathan Wakely

3
Secondo en.cppreference.com/w/cpp/language/auto , post C ++ 11, autoora viene utilizzato per la deduzione automatica del tipo Ma prima era utilizzato per specificare che si desiderava che la variabile fosse memorizzata "automaticamente" ( quindi sullo stack immagino) in contrasto con la parola chiave register(che significa "registro del processore"):
Guillaume

97

register è un suggerimento per il compilatore, consigliandogli di memorizzare quella variabile in un registro del processore invece che in memoria (ad esempio, invece che nello stack).

Il compilatore può o non può seguire quel suggerimento.

Secondo Herb Sutter in "Parole chiave che non sono (o commenti con un altro nome)" :

Uno specificatore di registro ha la stessa semantica di uno specificatore automatico ...


2
Tuttavia, a partire da C ++ 17, è deprecato, inutilizzato e riservato.
ZachB

@ ZachB, questo non è corretto; registro è riservato in C ++ 17 ma ancora funziona e funziona in modo quasi identico al registro di C.
Lewis Kelsey

@LewisKelsey È inutilizzato e riservato nelle specifiche C ++ 17; non è uno dei storage-class-specifiernella grammatica e non ha una semantica definita. Un compilatore conforme può generare un errore come fa Clang. Tuttavia alcune implementazioni lo consentono ancora e lo ignorano (MSVC, ICC) o lo usano come suggerimento per l'ottimizzazione (GCC). Vedi open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html . Tuttavia, ho sbagliato a parlare su un punto: era deprecato in C ++ 11.
ZachB


26

Con i compilatori di oggi, probabilmente niente. In origine era un suggerimento per inserire una variabile in un registro per un accesso più rapido, ma la maggior parte dei compilatori oggi ignora quel suggerimento e decide da sé.


9

Quasi certamente niente.

registerè un suggerimento per il compilatore che prevedi di usare xmolto e che pensi che dovrebbe essere inserito in un registro.

Tuttavia, i compilatori ora sono molto più bravi nel determinare quali valori dovrebbero essere inseriti nei registri rispetto al programmatore medio (o anche esperto), quindi i compilatori ignorano la parola chiave e fanno quello che vogliono.



7

La registerparola chiave è stata utile per:

  • Assemblaggio in linea.
  • Programmazione C / C ++ esperta.
  • Dichiarazione delle variabili memorizzabili nella cache.

Un esempio di sistema produttivo, dove registerera richiesta la parola chiave:

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

È stato deprecato da C ++ 11 ed è inutilizzato e riservato in C ++ 17 .


2
E aggiungerei che la parola chiave "register" sarebbe utile solo su un microcontrollore che esegue un singolo programma C ++ senza thread e senza multi tasking. Il programma C ++ dovrebbe possedere l'intera CPU per assicurarsi che la variabile "register" non venga spostata dagli speciali registri della CPU.
Santiago Villafuerte

@SantiagoVillafuerte vuoi aggiungerlo modificando la risposta?
ncomputers

Non sono così sicuro della mia risposta ... anche se sembra plausibile. Preferisco lasciarlo come commento in modo che gli altri lo approvino o lo disapprovino.
Santiago Villafuerte

1
@SantiagoVillafuerte Questo non è effettivamente vero, nei sistemi multitasking quando il cambio di contesto del sistema operativo, non dell'applicazione, è responsabile del salvataggio / ripristino dei registri. Dal momento che non stai cambiando contesto dopo ogni istruzione della CPU, mettere le cose nei registri è assolutamente significativo. Le altre risposte qui (che i compilatori semplicemente non si preoccupano della tua opinione quando si tratta dell'allocazione dei registri) sono più accurate.
cubo

L'esempio che hai mostrato utilizza in realtà l'estensione Explicit Register Variables di GCC , che è diversa dallo registerspecificatore della classe di archiviazione ed è ancora supportata da GCC.
ZachB

1

Si consideri un caso in cui l'ottimizzatore del compilatore ha due variabili ed è costretto a riversarne una nello stack. È successo così che entrambe le variabili hanno lo stesso peso per il compilatore. Dato che non c'è differenza, il compilatore riverserà arbitrariamente una delle variabili. D'altra parte, la registerparola chiave fornisce al compilatore un suggerimento su quale variabile sarà accessibile più frequentemente. È simile all'istruzione di prefetch x86, ma per l'ottimizzatore del compilatore.

Ovviamente i registersuggerimenti sono simili ai suggerimenti di probabilità di branch forniti dall'utente e possono essere dedotti da questi suggerimenti di probabilità. Se il compilatore sa che qualche ramo viene preso spesso, manterrà le variabili relative al ramo nei registri. Quindi suggerisco di preoccuparti di più dei suggerimenti sui rami e di dimenticarli register. Idealmente il tuo profiler dovrebbe comunicare in qualche modo con il compilatore e risparmiarti persino di pensare a tali sfumature.


1

A partire da gcc 9.3, la compilazione usando -std=c++2a, register produce un avviso del compilatore, ma ha ancora l'effetto desiderato e si comporta in modo identico al C registerquando si compila senza -O1 –- Flag di ottimizzazione veloce rispetto a questa risposta. L'uso di clang ++ - 7 causa tuttavia un errore del compilatore. Quindi sì,register ottimizzazioni fanno la differenza solo nella compilazione standard senza flag di ottimizzazione -O, ma sono ottimizzazioni di base che il compilatore potrebbe capire anche con -O1.

L'unica differenza è che in C ++, puoi prendere l'indirizzo della variabile di registro, il che significa che l'ottimizzazione avviene solo se non prendi l'indirizzo della variabile o dei suoi alias (per creare un puntatore) o prendi un riferimento di esso nel codice (solo su - O0, perché un riferimento ha anche un indirizzo, perché è un puntatore const nello stack , che, come un puntatore può essere ottimizzato fuori dallo stack se si compila usando -Ofast, tranne che non appariranno mai sullo stack usando -Ofast, perché a differenza di un puntatore, non possono essere creati volatilee i loro indirizzi non possono essere presi), altrimenti si comporterà come se non avessi usatoregister e il valore verrà memorizzato nello stack.

Su -O0, un'altra differenza è che const registersu gcc C e gcc C ++ non si comportano allo stesso modo. Su gcc C, const registersi comporta come register, perché i block-scope constnon sono ottimizzati su gcc. Su clang C, registernon fa nulla e si constapplicano solo le ottimizzazioni dell'ambito del blocco. Su gcc C, le registerottimizzazioni si applicano ma constnell'ambito del blocco non ha ottimizzazione. Su gcc C ++, sia registere constottimizzazioni block-scope combinano.

#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

registerdice al compilatore di 1) memorizzare una variabile locale in un registro salvato chiamato, in questo caso rbx, e 2) ottimizzare le scritture dello stack se l'indirizzo della variabile non viene mai preso . constdice al compilatore di sostituire immediatamente il valore (invece di assegnargli un registro o caricarlo dalla memoria) e di scrivere la variabile locale nello stack come comportamento predefinito. const registerè la combinazione di queste ottimizzazioni incoraggiate. Questo è il più sottile possibile.

Inoltre, su gcc C e C ++, register da solo sembra creare un gap casuale di 16 byte nello stack per il primo locale nello stack, cosa che non accade con const register.

Compilare usando -Ofast comunque; registerha un effetto di ottimizzazione 0 perché se può essere inserito in un registro o reso immediato, lo sarà sempre e se non può non lo sarà; constottimizza ancora il carico su C e C ++ ma solo nell'ambito del file ; volatileforza ancora i valori da memorizzare e caricare dallo stack.

.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
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.