Co to znaczy deklarować zmienną zmienną?

9

Wiele programów niskiego poziomu używa zmiennego słowa kluczowego dla typów do mapowania pamięci i tak dalej, jednak jestem trochę zdezorientowany co do tego, co NAPRAWDĘ robi w tle. Innymi słowy, co to znaczy, że kompilator nie „optymalizuje” adresu pamięci?

Vikaton
źródło
1
Jeśli odczytujesz swój wiek ze volatilezmiennej, która mówi 5, i czytasz ją ponownie w przyszłym roku, masz gwarancję, że dostaniesz 6.
5gon12eder
@ 5gon12eder, rozumiem, że zmienność oznacza, że ​​coś podlega szybkiej i łatwej zmianie, ale jak to działa? : S
Vikaton
Ponadto, w zależności od flag kompilacji, nieulotna zmienna „może” pojawić się w debuggerze (na przykład używasz kodu C + Eclipse + gdb), jako: „zoptymalizowana wartość”, ponieważ wartość zmiennej jest teraz gdzieś w rejestrze. Jeśli nie wiesz, jak korzystać z narzędzi / funkcji debugowania w asemblerze, po prostu zadeklaruj zmienną za pomocą modyfikatora ulotności.

Odpowiedzi:

11

volatile oznacza, że ​​jakiś inny procesor lub urządzenie I / O lub coś może zmienić zmienną spod ciebie.

W przypadku zwykłej zmiennej kroki twojego programu są jedyną rzeczą, która ją zmieni. Na przykład, jeśli czytasz 5zmienną i jej nie zmieniasz, nadal będzie zawierała 5. Ponieważ możesz na tym polegać, twój program nie musi poświęcić czasu na ponowne odczytanie zmiennej następnym razem, gdy będziesz chciał jej użyć. Kompilator C ++ jest inteligentny do generowania kodu, który po prostu pamięta 5.

Ale możesz to odczytać jako 5, być może wtedy system ładuje dane z dysku do tej pamięci, zmieniając je na 500. Jeśli chcesz, aby Twój program odczytał nową wartość 500, potrzebujesz, aby kompilator nie był zbyt inteligentny w używaniu poprzednio odczytanego 5. Musisz powiedzieć mu, aby za każdym razem ładował wartość. Właśnie to volatilerobi.

Analogia dla 5-latków
Załóżmy, że stawiasz duży arkusz papieru na stole. W jednym rogu papieru, należy zanotować aktualny wynik trwającej grze 3 to 4. Następnie idziesz na drugą stronę stołu i zaczynasz pisać historię o grze. Twój przyjaciel, który ogląda grę, aktualizuje wynik w tym rogu w trakcie gry. Kasuje 3 to 4i pisze 3 to 5.

Gdy umieścisz wynik gry w swojej historii, możesz:

  1. Zapisz ostatni wynik, który przeczytałeś, 3 to 4wesoło zakładając, że się nie zmienił (lub nie przejmując się, jeśli tak się stało), lub
  2. Przejdź po przeciwnej stronie stołu, aby przeczytać bieżący wynik (który jest 3 to 5teraz) i wróć. Tak volatiledziała zmienna.
Jerry101
źródło
11

volatile oznacza dwie rzeczy:

  1. Wartość zmiennej może ulec zmianie bez zmiany twojego kodu. Dlatego, gdy kompilator odczytuje wartość zmiennej, to może nie przyjąć, że jest taka sama jak ostatni raz było czytać, lub że jest taki sam, jak ostatniej wartości przechowywanej, ale należy jeszcze raz przeczytać.

  2. Zapisywanie wartości zmiennej lotnej jest „efektem ubocznym”, który można zaobserwować z zewnątrz, więc kompilatorowi nie wolno usuwać zapisu wartości; na przykład jeśli dwie wartości są przechowywane w jednym rzędzie, to kompilator musi faktycznie zapisać tę wartość dwukrotnie.

Jako przykład:

i = 2; 
i = i; 

Kompilator musi zapisać numer dwa, odczytać zmienną i, zapisać zmienną, którą odczytał do i.

Jest jeszcze inna sytuacja: jeśli funkcja używa, setjmpa następnie longjmpjest wywoływana, gwarantuje się, że wszystkie zmienne lokalne zmienne funkcji mają ostatnią zapamiętaną wartość - nie jest tak w przypadku nieulotnych zmiennych lokalnych.

gnasher729
źródło
Tutaj są pewne subtelne problemy. Jednym z subtelnych problemów jest to, że scharakteryzowałeś zmienne odczyty jako cechę zmiennej, podczas gdy w rzeczywistości są one charakterystyczne dla sposobu dostępu do zmiennej . Jeśli mamy zmienną ii wartość pi = &i, wówczas x = *pidokonuje odczytu z i, ale nie gwarantuje się, że ten odczyt będzie miał zmienną semantykę.
Eric Lippert,
1
@EricLippert: Jeśli izadeklarowano jako, volatile int ito pinależy zadeklarować jako volatile int *pi, w którym *pito przypadku dostęp jest niestabilny, nie?
Jon Purdy,
2

Wyjaśnienie abstrakcyjne
Zarówno C, jak i C ++ mają koncepcję abstrakcyjnej maszyny . Gdy kod używa wartości jakiejś zmiennej, maszyna abstrakcyjna mówi, że implementacja musi uzyskać dostęp do wartości tej zmiennej. Kod formularza statement_A; statement_B; statement_C;należy wykonać w dokładnie określonej kolejności. Wyrażenia wspólne dla tych trzech instrukcji muszą być ponownie obliczane za każdym razem, gdy występują.

W maszynach abstrakcyjnych, biorąc pod uwagę sekwencję instrukcji statement_A; statement_B; statement_C;, implementacja musi najpierw działać statement_Aw całości, a następnie w statement_Bkońcu statement_C. Implementacja nie pamięta, że ​​przypisałeś agewartość 5. Każda instrukcja, do której się odwołuje, agemusi zamiast tego uzyskać dostęp do wartości tej zmiennej.

Słowo kluczowe nie byłoby potrzebne, volatilejeśli implementacje ściśle wykonałyby kod C lub C ++ zgodnie ze specyfikacjami abstrakcyjnej maszyny. Maszyny abstrakcyjne C i C ++ nie mają pojęcia rejestrów, nie są powszechnie stosowane podwyrażenia, a kolejność wykonywania jest ścisła.

Oba języki mają również takie same zasady. Implementacja jest zgodna ze standardem, pod warunkiem, że implementacja zachowuje się tak, jakby wykonała wszystko zgodnie ze specyfikacją abstrakcyjnej maszyny. Kompilator może zakładać, że zmienne nielotne nie zmieniają wartości między przypisaniami. Dopóki nie złamie as-ifreguły, sekwencja statement_A; statement_B; statement_C;może zostać zaimplementowana przez wykonanie części statement_C, następnie części statement_A, następnie wszystkich statement_B, a następnie reszty statement_A, a na końcu reszty statement_C.

Te zasady „ jak gdyby” nie mają zastosowania do volatilezmiennych. W odniesieniu do volatilezmiennych i funkcji, implementacja musi robić dokładnie to, co jej nakazałeś, i dokładnie w takiej kolejności, w jakiej kazałeś to robić.

Ta abstrakcyjna specyfikacja maszyny ma wadę: jest powolna. Pozytywnym aspektem C i C ++ w porównaniu do innych języków jest to, że są one dość szybkie. Nie byłoby tak, gdyby kod został wykonany dla tych abstrakcyjnych maszyn. Na jak-jeśli zasady są co umożliwi C i C ++ się tak szybko.

Odpowiedź ELI5

co to znaczy, że kompilator nie „optymalizuje” adresu pamięci?

„Optymalizacja” adresu pamięci to zaawansowana koncepcja, coś, co nie mieści się w zakresie możliwości pięciolatka. Pięciolatki zgodne z przepisami zrobią dokładnie to, co im każesz, nie więcej, nie mniej. Dzięki volatile, mówisz realizację zachowywać się jak to jest pięć: Nie myślenia, nie ma optymalizacje fantazyjne. Zamiast tego implementacja musi robić dokładnie to, co nakazuje kod.

David Hammen
źródło
1

(nie) zmienna jest wskazówką dla kompilatora, w jaki sposób zoptymalizować kod (z punktu widzenia generowanego kodu asemblera):

  • nieulotna oznacza, że ​​twój obecny kompilator decyduje o tym, gdzie będzie się znajdować zmienna lub w jaki sposób wartość zmiennej jest przekazywana do podprogramu
    • pod stałym adresem pamięci,
    • na stosie [w stosunku do bieżącego wskaźnika stosu procesorów],
    • na stercie [w stosunku do bieżącej wskaźnika bazowego procesorów],
    • w rejestrze procesora,
    • ...
  • „niestabilny” oznacza, że ​​kompilator nie może zoptymalizować zmiennej, ponieważ coś innego poza kontrolerem głównego procesora (np. oddzielny procesor io) może zmienić tę wartość.
k3b
źródło
0

Odpowiedzi wydają się dość spójne, ale brakuje ważnego punktu. Mówisz kompilatorowi, że chcesz przydzielić miejsce i dla każdego dostępu, czytaj LUB NAPISZ, chcesz, aby ten dostęp był wykonywany. Z jakiegoś powodu nie chcemy, aby optymalizowała dostęp lub zmienną.

Tak, jednym z powodów jest to, że ktoś inny może dla nas zmienić tę wartość. Innym powodem może być zmiana tej wartości dla kogoś innego. Ktoś inny, to ten, który go zmienia dla nas lub ten, dla którego go zmieniamy, może być sprzętem / logiką lub oprogramowaniem. Jest często używany do definiowania dostępu do rejestrów kontrolnych i statusowych w programach osadzonych na gołym metalu, zapisywania lub odczytu ze sprzętu. Jak również oprogramowanie rozmawiające z oprogramowaniem wyjaśnione w innych odpowiedziach.

Zobaczysz również zmienność używaną do kontrolowania, kiedy i w jakiej kolejności mają się pojawiać dostępy, jeśli próbujesz zmierzyć sekcję kodu, i nie używasz zmienności, o których mowa (czas rozpoczęcia, czas zakończenia i różnica) muszą być tylko obliczony pod koniec kompilator może swobodnie przesuwać dowolny pomiar czasu (nie w miejscu, w którym go umieściliśmy), nie dlatego, że nie może się zmieniać przy zmienności, ale doświadczenie pokazuje, że jest mniej prawdopodobne.

Czasami zobaczysz, że po prostu pali czas, elementarny migacz ledowy, cześć świata z gołego metalu, może użyć lotnego dla zmiennej, która liczy się do dużej liczby, tylko po to, aby ludzkie oko zobaczyło led zmień stan. Bardziej zaawansowane przykłady niż użycie timerów lub innych zdarzeń do spalenia czasu.

old_timer
źródło