Zarejestruj słowo kluczowe w C ++

89

Jaka jest różnica między

int x=7;

i

register int x=7;

?

Używam C ++.

dato datuashvili
źródło
8
@GMan: ANSI C nie pozwala na pobranie adresu obiektu rejestru; to ograniczenie nie dotyczy C ++
Brian R. Bondy
1
@Brian: Hm, masz rację. Jest to teraz tylko w notatce (że prawdopodobnie zostanie zignorowane, jeśli adres zostanie zajęty), ale nie jest wymagane. Dobrze wiedzieć. (Cóż, coś w rodzaju.: P)
GManNickG
8
Głosowanie za ponownym otwarciem registerma inną semantykę między C i C ++.
CB Bailey,
3
w konsekwencji w C można zabronić konwersji tablicy na wskaźnik, tworząc rejestr tablicowy: register int a[1];z tą deklaracją nie można indeksować tej tablicy. Jeśli spróbujesz, zrobisz UB
Johannes Schaub - litb
2
Rzeczywiście, głosowałem za ponownym otwarciem. Głosowałem za zamknięciem, zanim wiedziałem, że jest różnica.
GManNickG,

Odpowiedzi:

24

W C ++, tak jak istniał w 2010 roku, każdy poprawny program, który używa słów kluczowych „auto” lub „register”, będzie semantycznie identyczny z programem z usuniętymi tymi słowami kluczowymi (chyba że pojawiają się one w makrach z ciągami znaków lub w innych podobnych kontekstach). W tym sensie słowa kluczowe są bezużyteczne do poprawnego kompilowania programów. Z drugiej strony, słowa kluczowe mogą być przydatne w niektórych kontekstach makr, aby zapewnić, że niewłaściwe użycie makra spowoduje błąd w czasie kompilacji, zamiast tworzyć fałszywy kod.

W C ++ 11 i późniejszych wersjach języka autosłowo kluczowe zostało ponownie użyte jako pseudo typ dla obiektów, które są inicjalizowane, które kompilator automatycznie zastąpi typem wyrażenia inicjującego. Tak więc w C ++ 03 deklaracja: auto int i=(unsigned char)5;była równoważna int i=5;używaniu w kontekście blokowym i auto i=(unsigned char)5;stanowiła naruszenie ograniczenia. W C ++ 11 auto int i=(unsigned char)5;stał się naruszeniem ograniczenia, a auto i=(unsigned char)5;stał się odpowiednikiem auto unsigned char i=5;.

supercat
źródło
22
Przydatny może być przykład ostatniego bitu.
Dennis Zickefoose
14
Ta odpowiedź nie jest już poprawna, ponieważ od 2011 r. Słowa kluczowego autonie można po prostu pominąć ... Być może mógłbyś zaktualizować swoją odpowiedź.
Walter,
2
@Walter: Czy możesz przytoczyć, co się zmieniło? Nie śledziłem wszystkich zmian językowych.
supercat
2
@supercat, tak, na razie, ale registerjest przestarzałe i pojawi się propozycja usunięcia go z C ++ 17.
Jonathan Wakely
3
Według en.cppreference.com/w/cpp/language/auto , po C ++ 11, autojest teraz używany do automatycznego odejmowania typów. Ale wcześniej używano go do określenia, że ​​chcesz, aby twoja zmienna była przechowywana "automatycznie" ( więc chyba na stosie ) w przeciwieństwie do słowa kluczowego register(oznaczającego „rejestr procesora”):
Guillaume
97

register jest wskazówką dla kompilatora, zalecającą przechowywanie tej zmiennej w rejestrze procesora zamiast w pamięci (na przykład zamiast stosu).

Kompilator może, ale nie musi, przestrzegać tej wskazówki.

Według Herb Suttera w „Keywords That Aren (or, Comments by Other Name)” :

Specyfikator rejestru ma taką samą semantykę jak specyfikator automatyczny ...

Tomek
źródło
2
Jednak od C ++ 17 jest przestarzały, nieużywany i zarezerwowany.
ZachB
@ZachB, to jest niepoprawne; register jest zarezerwowany w C ++ 17, ale nadal działa i działa prawie identycznie jak rejestr w C.
Lewis Kelsey
@LewisKelsey Jest nieużywany i zarezerwowany w specyfikacji C ++ 17; nie jest jednym z storage-class-specifierelementów gramatyki i nie ma zdefiniowanej semantyki. Zgodny kompilator może zgłosić błąd, tak jak robi to Clang. Niemniej jednak niektóre implementacje nadal na to pozwalają i albo je ignorują (MSVC, ICC), albo używają jako wskazówki optymalizacyjnej (GCC). Zobacz open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html . Jednak w jednym punkcie wypowiedziałem się źle: w C ++ 11 został uznany za przestarzały.
ZachB
26

Przy dzisiejszych kompilatorach prawdopodobnie nic. Oryginalnie była to wskazówka, aby umieścić zmienną w rejestrze w celu szybszego dostępu, ale większość dzisiejszych kompilatorów ignoruje tę wskazówkę i decyduje samodzielnie.

KeithB
źródło
9

Prawie na pewno nic.

registerjest wskazówką dla kompilatora, którego planujesz xdużo używać i że uważasz, że powinien on zostać umieszczony w rejestrze.

Jednak kompilatory są teraz znacznie lepsze w określaniu, jakie wartości powinny być umieszczane w rejestrach niż przeciętny (lub nawet ekspert) programista, więc kompilatory po prostu ignorują słowo kluczowe i robią, co chcą.

James Curran
źródło
7

Słowo registerkluczowe było przydatne do:

  • Montaż w linii.
  • Programowanie specjalistyczne w C / C ++.
  • Deklaracja zmiennych buforowalnych.

Przykład produktywnego systemu, w którym registersłowo kluczowe było wymagane:

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

Jest przestarzały od C ++ 11 i jest nieużywany i zarezerwowany w C ++ 17 .

nkomputery
źródło
2
I dodałbym, że słowo kluczowe „register” byłoby przydatne tylko w przypadku mikrokontrolera z pojedynczym programem w C ++ bez wątków i wielozadaniowości. Program C ++ musiałby posiadać cały procesor, aby mieć pewność, że zmienna „register” nie zostanie przeniesiona ze specjalnych rejestrów procesora.
Santiago Villafuerte
@SantiagoVillafuerte czy chcesz go dodać edytując odpowiedź?
ncomputers
Nie jestem pewien mojej odpowiedzi ... chociaż brzmi to wiarygodnie. Wolałbym zostawić to jako komentarz, aby inni go zaakceptowali lub odrzucili.
Santiago Villafuerte
1
@SantiagoVillafuerte W rzeczywistości nie jest to prawdą, w systemach wielozadaniowych, gdy przełączanie kontekstu to system operacyjny - a nie aplikacja - jest odpowiedzialny za zapisywanie / przywracanie rejestrów. Ponieważ nie przełączasz kontekstu po każdej instrukcji procesora, umieszczanie rzeczy w rejestrach jest absolutnie sensowne. Inne odpowiedzi tutaj (że kompilatorzy po prostu nie dbają o twoją opinię, jeśli chodzi o alokację rejestrów) są dokładniejsze.
Sześcienny
Przykład, który pokazałeś, w rzeczywistości używa rozszerzenia Explicit Register Variables , które różni się od specyfikatora registerklasy pamięci i jest nadal obsługiwane przez GCC.
ZachB
1

Rozważmy przypadek, gdy optymalizator kompilatora ma dwie zmienne i jest zmuszony wylać jedną na stos. Tak się złożyło, że obie zmienne mają taką samą wagę dla kompilatora. Biorąc pod uwagę, że nie ma różnicy, kompilator arbitralnie rozleje jedną ze zmiennych. Z drugiej strony registersłowo kluczowe daje kompilatorowi wskazówkę, która zmienna będzie używana częściej. Jest podobna do instrukcji pobierania wstępnego x86, ale służy do optymalizacji kompilatora.

Oczywiście registerwskazówki są podobne do wskazówek dotyczących prawdopodobieństwa gałęzi podanych przez użytkownika i można je wywnioskować z tych wskazówek dotyczących prawdopodobieństwa. Jeśli kompilator wie, że jakaś gałąź jest często brana, zachowa w rejestrach zmienne związane z gałęziami. Dlatego radzę bardziej dbać o wskazówki dotyczące gałęzi i zapomnieć o nich register. W idealnym przypadku Twój profiler powinien w jakiś sposób komunikować się z kompilatorem i oszczędzić Ci nawet myślenia o takich niuansach.

SmugLispWeenie
źródło
1

Jak gcc 9.3, kompilacja użyciu -std=c++2a, register daje ostrzeżenie kompilatora, ale wciąż ma pożądany efekt i zachowuje się identycznie jak na C registerprzy kompilacji bez -O1 - Ofast flagi optymalizacji w odniesieniu do tej odpowiedzi. Jednak użycie clang ++ - 7 powoduje błąd kompilatora. Więc tak, registeroptymalizacje robią różnicę tylko w standardowej kompilacji bez optymalizacji flag -O, ale są to podstawowe optymalizacje, które kompilator wymyśli nawet z -O1.

Jedyną różnicą jest to, że w C ++ możesz wziąć adres zmiennej register, co oznacza, że ​​optymalizacja ma miejsce tylko wtedy, gdy nie weźmiesz adresu zmiennej lub jej aliasów (aby utworzyć wskaźnik) lub skorzystasz z referencji tego w kodzie (tylko włączone - O0, ponieważ referencja również ma adres, ponieważ jest to wskaźnik do stałej na stosie , który podobnie jak wskaźnik może być zoptymalizowany poza stosem, jeśli kompiluje się z użyciem -Ofast, z wyjątkiem tego, że nigdy się nie pojawią na stosie za pomocą -Ofast, ponieważ w przeciwieństwie do wskaźnika nie można ich utworzyć, volatilea ich adresy nie mogą zostać pobrane), w przeciwnym razie będzie zachowywał się tak, jak nie był używany register, a wartość zostanie zapisana na stosie.

Przy -O0 kolejną różnicą jest to, że const registerna gcc C i gcc C ++ nie zachowują się tak samo. Na gcc C const registerzachowuje się jak register, ponieważ zakresy blokowe constnie są optymalizowane w gcc. Na clang C registernic nie robi i stosowane są tylko constoptymalizacje w zakresie blokowym. W gcc C registeroptymalizacje mają zastosowanie, ale constw zakresie blokowym nie ma optymalizacji. Na gcc C ++, zarówno registeri constoptymalizacje bloku zakres połączyć.

#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

registernakazuje kompilatorowi 1) przechowywać zmienną lokalną w zapisanym rejestrze wywoływanym, w tym przypadku rbx, i 2) optymalizować zapisy na stosie, jeśli adres zmiennej nigdy nie jest pobierany . constnakazuje kompilatorowi natychmiastowe podstawienie wartości (zamiast przypisywania jej rejestru lub ładowania z pamięci) i zapisanie zmiennej lokalnej na stosie jako zachowanie domyślne. const registerto połączenie tych śmiałych optymalizacji. To jest tak smukłe, jak to tylko możliwe.

Również w gcc C i C ++ registerwydaje się, że samodzielnie tworzy losową 16-bajtową lukę na stosie dla pierwszego lokalu lokalnego na stosie, co nie ma miejsca w przypadku const register.

Jednak kompilowanie przy użyciu -Ofast; registerma 0 efekt optymalizacji, ponieważ jeśli można go umieścić w rejestrze lub uczynić natychmiastowym, to zawsze będzie, a jeśli nie, to nie będzie; constnadal optymalizuje obciążenie w C i C ++, ale tylko w zakresie pliku ; volatilenadal wymusza zapisywanie i ładowanie wartości ze stosu.

.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
źródło