@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;.
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.
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.
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.
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ą.
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.
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++intmain(void){
constregisterint i = 3;
printf("%d", i);
return0;
}
.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 printfxor eax, eax
add rsp, 8
ret
register
ma inną semantykę między C i C ++.register int a[1];
z tą deklaracją nie można indeksować tej tablicy. Jeśli spróbujesz, zrobisz UBOdpowiedzi:
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
auto
sł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żnaint i=5;
używaniu w kontekście blokowym iauto i=(unsigned char)5;
stanowiła naruszenie ograniczenia. W C ++ 11auto int i=(unsigned char)5;
stał się naruszeniem ograniczenia, aauto i=(unsigned char)5;
stał się odpowiednikiemauto unsigned char i=5;
.źródło
auto
nie można po prostu pominąć ... Być może mógłbyś zaktualizować swoją odpowiedź.register
jest przestarzałe i pojawi się propozycja usunięcia go z C ++ 17.auto
jest 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 kluczowegoregister
(oznaczającego „rejestr procesora”):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)” :
źródło
storage-class-specifier
elementó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.Według Herb Sutter ,
register
to „ dokładnie tak znaczące jak białe znaki ” i nie ma wpływu na semantyki programu C ++.źródło
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.
źródło
Prawie na pewno nic.
register
jest wskazówką dla kompilatora, którego planujeszx
duż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ą.
źródło
register
jest przestarzałe w C ++ 11. Jest nieużywany i zarezerwowany w C ++ 17.Źródło: http://en.cppreference.com/w/cpp/keyword/register
źródło
Słowo
register
kluczowe było przydatne do:Przykład produktywnego systemu, w którym
register
sł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 .
źródło
register
klasy pamięci i jest nadal obsługiwane przez GCC.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
register
sł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
register
wskazó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 nichregister
. 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.źródło
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 Cregister
przy kompilacji bez -O1 - Ofast flagi optymalizacji w odniesieniu do tej odpowiedzi. Jednak użycie clang ++ - 7 powoduje błąd kompilatora. Więc tak,register
optymalizacje 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ć,
volatile
a ich adresy nie mogą zostać pobrane), w przeciwnym razie będzie zachowywał się tak, jak nie był używanyregister
, a wartość zostanie zapisana na stosie.Przy -O0 kolejną różnicą jest to, że
const register
na gcc C i gcc C ++ nie zachowują się tak samo. Na gcc Cconst register
zachowuje się jakregister
, ponieważ zakresy blokoweconst
nie są optymalizowane w gcc. Na clang Cregister
nic nie robi i stosowane są tylkoconst
optymalizacje w zakresie blokowym. W gcc Cregister
optymalizacje mają zastosowanie, aleconst
w zakresie blokowym nie ma optymalizacji. Na gcc C ++, zarównoregister
iconst
optymalizacje 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
register
nakazuje kompilatorowi 1) przechowywać zmienną lokalną w zapisanym rejestrze wywoływanym, w tym przypadkurbx
, i 2) optymalizować zapisy na stosie, jeśli adres zmiennej nigdy nie jest pobierany .const
nakazuje 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 register
to połączenie tych śmiałych optymalizacji. To jest tak smukłe, jak to tylko możliwe.Również w gcc C i C ++
register
wydaje się, że samodzielnie tworzy losową 16-bajtową lukę na stosie dla pierwszego lokalu lokalnego na stosie, co nie ma miejsca w przypadkuconst register
.Jednak kompilowanie przy użyciu -Ofast;
register
ma 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;const
nadal optymalizuje obciążenie w C i C ++, ale tylko w zakresie pliku ;volatile
nadal 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
źródło