Registrieren Sie das Schlüsselwort in C ++

87

Was ist der Unterschied zwischen

int x=7;

und

register int x=7;

?

Ich benutze C ++.

dato datuashvili
quelle
8
@GMan: ANSI C erlaubt nicht, die Adresse eines Registerobjekts zu übernehmen. Diese Einschränkung gilt nicht für C ++
Brian R. Bondy
1
@ Brian: Hm, du hast recht. Es ist jetzt nur in einer Notiz (dass es wahrscheinlich ignoriert wird, wenn die Adresse genommen wird), aber nicht vorgeschrieben. Gut zu wissen. (Nun, irgendwie .: P)
GManNickG
8
Abstimmung zur Wiedereröffnung register hat eine unterschiedliche Semantik zwischen C und C ++.
CB Bailey
3
Infolgedessen ist es in C möglich, die Umwandlung von Array in Zeiger zu verbieten, indem ein Array-Register erstellt wird: register int a[1];Mit dieser Deklaration können Sie dieses Array nicht indizieren. Wenn Sie es versuchen, machen Sie UB
Johannes Schaub - litb
2
In der Tat habe ich für die Wiedereröffnung gestimmt. Ich habe für das Schließen gestimmt, bevor ich wusste, dass es einen Unterschied gibt.
GManNickG

Antworten:

25

In C ++, wie es 2010 existierte, ist jedes gültige Programm, das die Schlüsselwörter "auto" oder "register" verwendet, semantisch identisch mit einem Programm, bei dem diese Schlüsselwörter entfernt wurden (es sei denn, sie erscheinen in stringierten Makros oder anderen ähnlichen Kontexten). In diesem Sinne sind die Schlüsselwörter für das ordnungsgemäße Kompilieren von Programmen unbrauchbar. Andererseits können die Schlüsselwörter in bestimmten Makrokontexten nützlich sein, um sicherzustellen, dass eine unsachgemäße Verwendung eines Makros einen Fehler bei der Kompilierung verursacht, anstatt falschen Code zu erzeugen.

In C ++ 11 und späteren Versionen der Sprache wurde das autoSchlüsselwort neu definiert, um als Pseudotyp für initialisierte Objekte zu fungieren, die ein Compiler automatisch durch den Typ des initialisierenden Ausdrucks ersetzt. Daher war in C ++ 03 die Deklaration: auto int i=(unsigned char)5;äquivalent zu, int i=5;wenn sie in einem Blockkontext verwendet wurde, und auto i=(unsigned char)5;war eine Einschränkungsverletzung. In C ++ 11 auto int i=(unsigned char)5;wurde eine Einschränkungsverletzung, während sie auto i=(unsigned char)5;äquivalent zu wurde auto unsigned char i=5;.

Superkatze
quelle
22
Ein Beispiel für das letzte Bit kann nützlich sein.
Dennis Zickefoose
14
Diese Antwort ist nicht mehr korrekt, da das Schlüsselwort seit 2011 autonicht einfach weggelassen werden kann ... Vielleicht können Sie Ihre Antwort aktualisieren.
Walter
2
@Walter: Kannst du zitieren, was sich geändert hat? Ich habe nicht alle Sprachänderungen verfolgt.
Supercat
2
@supercat, ja, im Moment, aber registerveraltet und es wird einen Vorschlag geben, es für C ++ 17 zu entfernen.
Jonathan Wakely
3
Laut en.cppreference.com/w/cpp/language/auto wird Post C ++ 11 autojetzt für den automatischen Typabzug verwendet. Zuvor wurde jedoch angegeben, dass Ihre Variable "automatisch" gespeichert werden soll ( daher auf dem Stapel, denke ich) im Gegensatz zum Schlüsselwort register(was "Prozessorregister" bedeutet):
Guillaume
96

register ist ein Hinweis für den Compiler, der ihm empfiehlt, diese Variable in einem Prozessorregister anstelle des Speichers (z. B. anstelle des Stapels) zu speichern.

Der Compiler kann diesem Hinweis folgen oder nicht.

Laut Herb Sutter in "Keywords That Aren't (oder Kommentare von einem anderen Namen)" :

Ein Registerspezifizierer hat dieselbe Semantik wie ein automatischer Spezifizierer ...

Tom
quelle
2
Seit C ++ 17 ist es jedoch veraltet, unbenutzt und reserviert.
ZachB
@ZachB, das ist falsch; Register wird in C ++ reserviert 17 , aber es immer noch funktioniert und funktioniert fast genauso C des Registers.
Lewis Kelsey
@ LewisKelsey Es wird nicht verwendet und ist in der C ++ 17-Spezifikation reserviert. Es gehört nicht storage-class-specifierzur Grammatik und hat keine definierte Semantik. Ein konformer Compiler kann wie Clang einen Fehler auslösen. Einige Implementierungen erlauben es jedoch weiterhin und ignorieren es entweder (MSVC, ICC) oder verwenden es als Optimierungshinweis (GCC). Siehe open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html . Ich habe jedoch in einem Punkt falsch gesprochen: Es war in C ++ 11 veraltet.
ZachB
25

Mit den heutigen Compilern wahrscheinlich nichts. Es war ursprünglich ein Hinweis, eine Variable für einen schnelleren Zugriff in ein Register zu stellen, aber die meisten Compiler ignorieren diesen Hinweis heute und entscheiden selbst.

KeithB
quelle
8

Mit ziemlicher Sicherheit nichts.

register ist ein Hinweis auf den Compiler, den Sie verwenden möchten x viel möchten und dass Sie der Meinung sind, dass es in ein Register gestellt werden sollte.

Compiler können jetzt jedoch weitaus besser bestimmen, welche Werte in Registern abgelegt werden sollen als der durchschnittliche (oder sogar erfahrene) Programmierer. Daher ignorieren Compiler das Schlüsselwort einfach und tun, was sie wollen.

James Curran
quelle
7

Das registerSchlüsselwort war nützlich für:

  • Inline-Montage.
  • Erfahrene C / C ++ - Programmierung.
  • Deklaration von zwischenspeicherbaren Variablen.

Ein Beispiel für ein Produktivsystem, bei dem das registerSchlüsselwort erforderlich war:

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

Es ist seit C ++ 11 veraltet und wird in C ++ 17 nicht verwendet und reserviert .

ncomputer
quelle
2
Und ich würde hinzufügen, dass das Schlüsselwort 'register' nur auf einem Mikrocontroller nützlich wäre, auf dem ein einzelnes C ++ - Programm ohne Threads und ohne Multitasking ausgeführt wird. Das C ++ - Programm müsste die gesamte CPU besitzen, um sicherzustellen, dass die Variable 'register' nicht aus den speziellen CPU-Registern verschoben wird.
Santiago Villafuerte
@ SantiagoVillafuerte Möchten Sie es hinzufügen, um die Antwort zu bearbeiten?
ncomputers
Ich bin mir meiner Antwort nicht so sicher ... obwohl es plausibel klingt. Ich würde es vorziehen, es als Kommentar zu hinterlassen, damit andere es genehmigen oder ablehnen.
Santiago Villafuerte
1
@SantiagoVillafuerte Dies ist in Multitasking-Systemen nicht der Fall, wenn die Kontextumschaltung des Betriebssystems - nicht der Anwendung - für das Speichern / Wiederherstellen von Registern verantwortlich ist. Da Sie nicht nach jedem CPU-Befehl den Kontext wechseln, ist es absolut sinnvoll, Dinge in Register zu setzen. Die anderen Antworten hier (dass Compiler sich einfach nicht um Ihre Meinung kümmern, wenn es um die Zuweisung von Registern geht) sind genauer.
Cubic
Das Beispiel, das Sie gezeigt haben, verwendet tatsächlich die Erweiterung Explicit Register Variablesregister von GCC , die sich vom Speicherklassenspezifizierer unterscheidet und weiterhin von GCC unterstützt wird.
ZachB
1

Stellen Sie sich einen Fall vor, in dem der Optimierer des Compilers zwei Variablen enthält und gezwungen ist, eine auf den Stapel zu übertragen. So kam es, dass beide Variablen für den Compiler das gleiche Gewicht haben. Da es keinen Unterschied gibt, verschüttet der Compiler willkürlich eine der Variablen. Andererseits gibt das registerSchlüsselwort dem Compiler einen Hinweis, auf welche Variable häufiger zugegriffen wird. Es ähnelt der x86-Prefetch-Anweisung, ist jedoch für den Compiler-Optimierer vorgesehen.

Offensichtlich registerähneln Hinweise den vom Benutzer bereitgestellten Verzweigungswahrscheinlichkeitshinweisen und können aus diesen Wahrscheinlichkeitshinweisen abgeleitet werden. Wenn der Compiler weiß, dass ein Zweig häufig verwendet wird, werden verzweigungsbezogene Variablen in Registern gespeichert. Deshalb schlage ich vor, mich mehr um Zweighinweise zu kümmern und diese zu vergessen register. Idealerweise sollte Ihr Profiler irgendwie mit dem Compiler kommunizieren und Sie davon abhalten, über solche Nuancen nachzudenken.

SmugLispWeenie
quelle
1

Ab gcc 9.3 kompilieren mit -std=c++2a, register erzeugt eine Compiler - Warnung, aber es hat immer noch die gewünschte Wirkung und verhält sich identisch zu C ist , registerwenn sie ohne -O1 Kompilieren - Ofast Optimierungen im Hinblick auf diese Antwort. Die Verwendung von clang ++ - 7 verursacht jedoch einen Compilerfehler. Also ja,register Optimierungen machen nur bei der Standardkompilierung ohne Optimierungs -O-Flags einen Unterschied, aber es handelt sich um grundlegende Optimierungen, die der Compiler selbst mit -O1 herausfinden würde.

Der einzige Unterschied besteht darin, dass Sie in C ++ die Adresse der Registervariablen übernehmen dürfen. Dies bedeutet, dass die Optimierung nur erfolgt, wenn Sie nicht die Adresse der Variablen oder ihrer Aliase (um einen Zeiger zu erstellen) oder eine Referenz verwenden davon im Code (nur bei - O0, da eine Referenz hat auch eine Adresse, weil es ein const Zeiger auf dem Stapel ist , der, wie ein Zeiger kann vom Stapel optimiert werden , wenn mit -Ofast kompilieren, außer sie werden nie angezeigt auf dem Stapel mit -Ofast, da sie im Gegensatz zu einem Zeiger nicht erstellt volatileund ihre Adressen nicht verwendet werden können), andernfalls verhält es sich so, als hätten Sie sie nicht verwendetregister , und der Wert wird auf dem Stapel gespeichert.

Bei -O0 besteht ein weiterer Unterschied darin, dass sich const registergcc C und gcc C ++ nicht gleich verhalten. Verhält sich auf gcc C const registerwie folgt register, da Blockbereiche constauf gcc nicht optimiert sind. Bei Clang C gelten registernichts und nur constBlock-Scope-Optimierungen. Auf gcc C registergelten Optimierungen, constim Blockbereich jedoch keine Optimierung. In gcc C ++ werden sowohl Optimierungen registerals auch constBlockbereichsoptimierungen kombiniert.

#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

registerweist den Compiler an, 1) in diesem Fall eine lokale Variable in einem von einem Angerufenen gespeicherten Register zu speichern rbxund 2) Stapelschreibvorgänge zu optimieren, wenn die Adresse der Variablen niemals verwendet wird . constWeist den Compiler an, den Wert sofort zu ersetzen (anstatt ihm ein Register zuzuweisen oder ihn aus dem Speicher zu laden) und die lokale Variable als Standardverhalten in den Stapel zu schreiben. const registerist die Kombination dieser ermutigten Optimierungen. Dies ist so schlank wie es nur geht.

Außerdem registerscheint es unter gcc C und C ++ für sich genommen eine zufällige 16-Byte-Lücke im Stapel für das erste lokale Element im Stapel zu geben, was bei nicht der Fall ist const register.

Kompilieren mit -Ofast jedoch; registerhat 0 Optimierungseffekt, denn wenn es in ein Register aufgenommen oder sofort erstellt werden kann, wird es immer sein und wenn es nicht kann, wird es nicht sein; constoptimiert weiterhin die Auslastung von C und C ++, jedoch nur im Dateibereich ; volatileErzwingt weiterhin, dass die Werte gespeichert und vom Stapel geladen werden.

.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
Lewis Kelsey
quelle