Dlaczego jest volatile
potrzebny w C? Do czego jest to używane? Co to zrobi
c
declaration
volatile
Jonathan Leffler
źródło
źródło
Odpowiedzi:
Zmienna mówi kompilatorowi, aby nie optymalizował niczego, co ma związek ze zmienną zmienną.
Istnieją co najmniej trzy typowe powody, dla których warto go używać, wszystkie związane z sytuacjami, w których wartość zmiennej może się zmienić bez działania widocznego kodu: Kiedy łączysz się ze sprzętem, który zmienia samą wartość; gdy działa inny wątek, który również korzysta ze zmiennej; lub gdy istnieje moduł obsługi sygnału, który może zmienić wartość zmiennej.
Załóżmy, że masz mały sprzęt, który jest gdzieś zmapowany w pamięci RAM i ma dwa adresy: port poleceń i port danych:
Teraz chcesz wysłać polecenie:
Wygląda na łatwe, ale może się nie powieść, ponieważ kompilator może dowolnie zmieniać kolejność zapisywania danych i poleceń. Spowodowałoby to, że nasz mały gadżet wydawałby polecenia z poprzednią wartością danych. Spójrz także na pętlę oczekiwania podczas zajętości. Ten zostanie zoptymalizowany. Kompilator spróbuje być sprytny, odczyta wartość isbusy tylko raz, a następnie przejdzie w nieskończoną pętlę. Nie tego chcesz.
Aby obejść ten problem, zadeklaruj gadżet wskaźnika jako niestabilny. W ten sposób kompilator jest zmuszony do robienia tego, co napisałeś. Nie może usuwać przypisań pamięci, nie może buforować zmiennych w rejestrach i nie może zmieniać kolejności przypisań:
To jest poprawna wersja:
źródło
volatile
w C faktycznie powstało w celu nie buforowania wartości zmiennej automatycznie. Powie kompilatorowi, aby nie buforował wartości tej zmiennej. Wygeneruje więc kod, który pobierze wartość danejvolatile
zmiennej z pamięci głównej za każdym razem, gdy ją napotka. Ten mechanizm jest używany, ponieważ w dowolnym momencie wartość może być modyfikowana przez system operacyjny lub dowolne przerwanie. Używanievolatile
pomoże nam za każdym razem uzyskać dostęp do wartości.źródło
volatile
kompilatora było umożliwienie kompilatorom optymalizacji kodu, jednocześnie umożliwiając programistom osiągnięcie semantyki, która zostałaby osiągnięta bez takich optymalizacji. Autorzy Standardu oczekiwali, że implementacje jakościowe będą wspierać każdą semantykę, która byłaby użyteczna, biorąc pod uwagę ich docelowe platformy i pola aplikacji, i nie spodziewali się, że autorzy kompilatorów będą dążyć do zaoferowania semantyki o najniższej jakości, która jest zgodna ze Standardem i nie są w 100% głupie (zauważ, że autorzy Standardu wyraźnie uznają w uzasadnieniu ...Innym zastosowaniem
volatile
jest obsługa sygnałów. Jeśli masz taki kod:Kompilator może zauważyć, że ciało pętli nie dotyka
quit
zmiennej i przekształca pętlę wwhile (true)
pętlę. Nawet jeśliquit
zmienna jest ustawiona w module obsługi sygnałów dlaSIGINT
iSIGTERM
; kompilator nie ma sposobu, aby to wiedzieć.Jeśli jednak
quit
zmienna zostanie zadeklarowanavolatile
, kompilator będzie zmuszony ją ładować za każdym razem, ponieważ można ją zmodyfikować w innym miejscu. Właśnie tego chcesz w tej sytuacji.źródło
quit
, kompilator może zoptymalizować ją do stałej pętli, zakładając, że że nie ma możliwościquit
zmiany między iteracjami. Uwaga: niekoniecznie jest to dobry zamiennik dla rzeczywistego programowania wątkowego.volatile
lub innych znacznikach zakłada, że nic poza pętlą nie modyfikuje tej zmiennej po wejściu do pętli, nawet jeśli jest to zmienna globalna.extern int global; void fn(void) { while (global != 0) { } }
zgcc -O3 -S
i spojrzenie na pliku wynikowego montażowej, na moim komputerze to robimovl global(%rip), %eax
;testl %eax, %eax
;je .L1
;.L4: jmp .L4
, czyli nieskończona pętla, jeśli globalny nie jest równy zero. Następnie spróbuj dodaćvolatile
i zobacz różnicę.volatile
informuje kompilator, że zmienna może zostać zmieniona w inny sposób niż kod, który ma do niej dostęp. np. może to być lokalizacja pamięci odwzorowana we / wy. Jeśli nie zostanie to określone w takich przypadkach, niektóre zmienne dostępy można zoptymalizować, np. Jego zawartość może być przechowywana w rejestrze, a lokalizacja pamięci nie może zostać ponownie odczytana.źródło
Zobacz ten artykuł Andrei Alexandrescu, „ volatile - Najlepszy przyjaciel wielowątkowego programisty ”
Artykuł dotyczy zarówno
C
iC++
.Zobacz także artykuł „ C ++ i niebezpieczeństwa podwójnie sprawdzonego blokowania ” Scott Meyers i Andrei Alexandrescu:
źródło
volatile
nie gwarantuje atomowości.Moje proste wyjaśnienie to:
W niektórych scenariuszach, na podstawie logiki lub kodu, kompilator dokona optymalizacji zmiennych, które jego zdaniem nie ulegną zmianie. W
volatile
zapobiega kluczowych zmienne są zoptymalizowane.Na przykład:
Z powyższego kodu kompilator może pomyśleć, że
usb_interface_flag
jest zdefiniowany jako 0 i że w pętli while na zawsze będzie zero. Po optymalizacji kompilator będzie go traktował jakwhile(true)
cały czas, co spowoduje powstanie nieskończonej pętli.Aby uniknąć tego rodzaju scenariuszy, deklarujemy flagę jako niestabilną, informujemy kompilator, że ta wartość może zostać zmieniona przez zewnętrzny interfejs lub inny moduł programu, tzn. Nie optymalizuj jej. To jest przypadek użycia lotnych.
źródło
Krańcowe zastosowanie substancji lotnych jest następujące. Powiedzmy, że chcesz obliczyć pochodną numeryczną funkcji
f
:Problem w tym, że
x+h-x
generalnie nie jest równy zh
powodu błędów zaokrągleń. Pomyśl o tym: odejmując bardzo bliskie liczby, tracisz wiele znaczących cyfr, które mogą zrujnować obliczanie pochodnej (pomyśl 1.00001-1). Możliwym obejściem może byćale w zależności od platformy i przełączników kompilatora druga linia tej funkcji może zostać usunięta przez agresywnie optymalizujący kompilator. Zamiast tego piszesz
aby zmusić kompilator do odczytania lokalizacji pamięci zawierającej hh, rezygnując z ewentualnej możliwości optymalizacji.
źródło
h
lubhh
w formule pochodnej? Kiedyhh
jest obliczana, ostatnia formuła używa jej jak pierwszej, bez różnicy. Może powinno być(f(x+h) - f(x))/hh
?h
ihh
polega na tym, żehh
operacja ta obcina do pewnej ujemnej potęgi dwóchx + h - x
. W tym przypadkux + hh
ix
różnią się dokładnie ohh
. Możesz również wziąć swoją formułę, da to ten sam wynik, ponieważx + h
ix + hh
są równe (to mianownik jest tutaj ważny).x1=x+h; d = (f(x1)-f(x))/(x1-x)
? bez użycia substancji lotnych.-ffast-math
lub równoważne.Istnieją dwa zastosowania. Są one częściej używane częściej w programowaniu wbudowanym.
Kompilator nie zoptymalizuje funkcji wykorzystujących zmienne zdefiniowane za pomocą niestabilnego słowa kluczowego
Funkcja Volatile służy do uzyskiwania dostępu do dokładnych lokalizacji pamięci w pamięci RAM, ROM itp. Jest używana częściej do kontrolowania mapowanych urządzeń, uzyskiwania dostępu do rejestrów procesora i lokalizowania określonych lokalizacji pamięci.
Zobacz przykłady z listą zestawów. Re: Użycie słowa kluczowego „niestabilnego” w programowaniu wbudowanym
źródło
Lotny jest również przydatny, gdy chcesz zmusić kompilator do nieoptymalizowania określonej sekwencji kodu (np. Do napisania mikroprocesora).
źródło
Wspomnę o innym scenariuszu, w którym substancje lotne są ważne.
Załóżmy, że mapujesz pamięć pliku, aby uzyskać szybsze operacje we / wy, a plik ten może ulec zmianie za kulisami (np. Plik nie znajduje się na lokalnym dysku twardym, ale jest obsługiwany przez sieć przez inny komputer).
Jeśli uzyskasz dostęp do danych pliku odwzorowanego w pamięci za pośrednictwem wskaźników do obiektów nieulotnych (na poziomie kodu źródłowego), kod wygenerowany przez kompilator może pobrać te same dane wiele razy, nie zdając sobie z tego sprawy.
Jeśli te dane się zmienią, Twój program może korzystać z dwóch lub więcej różnych wersji danych i przejść w niespójny stan. Może to prowadzić nie tylko do logicznie niepoprawnego zachowania programu, ale także do luk w zabezpieczeniach, które mogą zostać wykorzystane, jeśli przetwarza niezaufane pliki lub pliki z niezaufanych lokalizacji.
Jeśli zależy Ci na bezpieczeństwie i powinieneś, jest to ważny scenariusz do rozważenia.
źródło
„niestabilna” oznacza, że pamięć prawdopodobnie zmieni się w dowolnym momencie i zostanie zmieniona, ale coś poza kontrolą programu użytkownika. Oznacza to, że jeśli odwołujesz się do zmiennej, program powinien zawsze sprawdzać adres fizyczny (tj. Mapowane wejście fifo) i nie używać go w pamięci podręcznej.
źródło
Wiki mówi wszystko o
volatile
:Dokumentacja jądra Linuksa stanowi również doskonały zapis na temat
volatile
:źródło
Moim zdaniem nie należy oczekiwać zbyt wiele
volatile
. Aby to zilustrować, spójrz na przykład w wysoko głosowanej odpowiedzi Nilsa Pipenbrincka .Powiedziałbym, że jego przykład nie jest odpowiedni
volatile
.volatile
służy tylko do: uniemożliwienia kompilatorowi wykonania przydatnych i pożądanych optymalizacji . Nie ma nic o bezpieczeństwie wątku, dostępie atomowym, a nawet porządku pamięci.W tym przykładzie:
gadget->data = data
przedgadget->command = command
tylko jest zagwarantowana tylko w skompilowanego kodu przez kompilator. W czasie wykonywania procesor nadal prawdopodobnie zmienia kolejność danych i przypisywanie poleceń w odniesieniu do architektury procesora. Sprzęt może uzyskać nieprawidłowe dane (załóżmy, że gadżet jest odwzorowany na sprzętowe We / Wy). Bariera pamięci jest potrzebna między przypisaniem danych a poleceniami.źródło
volatile
obniża wydajność bez powodu. To, czy będzie wystarczające, będzie zależeć od innych aspektów systemu, o których programiści mogą wiedzieć więcej niż kompilator. Z drugiej strony, jeśli procesor gwarantuje, że instrukcja zapisu pod określonym adresem opróżni pamięć podręczną procesora, ale kompilator nie zapewnił możliwości opróżnienia zmiennych buforowanych w rejestrze, o których procesor nic nie wie, opróżnienie pamięci podręcznej byłoby bezużyteczne.W języku zaprojektowanym przez Dennisa Ritchiego każdy dostęp do dowolnego obiektu, z wyjątkiem automatycznych obiektów, których adres nie został pobrany, zachowywałby się tak, jakby obliczał adres obiektu, a następnie czytał lub zapisywał pamięć pod tym adresem. To sprawiło, że język był bardzo mocny, ale znacznie ograniczył możliwości optymalizacji.
Chociaż możliwe byłoby dodanie kwalifikatora, który zachęciłby kompilator do założenia, że dany obiekt nie zostanie zmieniony w dziwny sposób, takie założenie byłoby odpowiednie dla zdecydowanej większości obiektów w programach C i miałoby niepraktyczne było dodanie kwalifikatora do wszystkich obiektów, dla których takie założenie byłoby odpowiednie. Z drugiej strony niektóre programy muszą korzystać z niektórych obiektów, dla których takie założenie nie miałoby miejsca. Aby rozwiązać ten problem, Standard mówi, że kompilatory mogą założyć, że obiekty, które nie zostały zadeklarowane
volatile
, nie będą miały obserwowanej lub zmienianej wartości w sposób, który jest poza kontrolą kompilatora lub będzie poza rozsądnym zrozumieniem kompilatora.Ponieważ różne platformy mogą mieć różne sposoby obserwowania lub modyfikowania obiektów poza kontrolą kompilatora, właściwe jest, aby kompilatory jakości dla tych platform różniły się pod względem dokładnej obsługi
volatile
semantyki. Niestety, ponieważ Standard nie zasugerował, że kompilatory jakości przeznaczone do programowania niskopoziomowego na platformie powinny obsługiwaćvolatile
w sposób, który rozpoznaje wszelkie odpowiednie efekty konkretnej operacji odczytu / zapisu na tej platformie, wiele kompilatorów nie radzi sobie więc w sposób, który utrudnia przetwarzanie takich operacji, jak We / Wy w tle, w sposób, który jest wydajny, ale nie może zostać zepsuty przez „optymalizacje” kompilatora.źródło
Mówiąc najprościej, informuje kompilator, aby nie przeprowadzał żadnej optymalizacji konkretnej zmiennej. Zmienne mapowane do rejestru urządzenia są modyfikowane pośrednio przez urządzenie. W takim przypadku należy użyć substancji lotnej.
źródło
Zmienny można zmienić spoza skompilowanego kodu (na przykład program może zmapować zmienną zmienną do rejestru odwzorowanego w pamięci). Kompilator nie zastosuje pewnych optymalizacji w kodzie, który obsługuje zmienną zmienną - na przykład wygrał załaduj go do rejestru bez zapisywania go w pamięci. Jest to ważne w przypadku rejestrów sprzętowych.
źródło
Jak słusznie sugeruje wielu tutaj, popularnym zastosowaniem lotnego słowa kluczowego jest pominięcie optymalizacji zmiennej lotnej.
Najlepszą korzyścią, która przychodzi mi do głowy i o której warto wspomnieć po przeczytaniu o niestabilności, jest - zapobieganie cofnięciu zmiennej w przypadku
longjmp
. Nielokalny skok.Co to znaczy?
Oznacza to po prostu, że ostatnia wartość zostanie zachowana po cofnięciu stosu , aby powrócić do poprzedniej ramki stosu; zazwyczaj w przypadku jakiegoś błędnego scenariusza.
Ponieważ byłoby to poza zakresem tego pytania, nie będę tu wchodził w szczegóły
setjmp/longjmp
, ale warto o tym przeczytać; i jak można wykorzystać funkcję zmienności do zachowania ostatniej wartości.źródło
nie pozwala kompilatorowi na automatyczną zmianę wartości zmiennych. zmienna lotna służy do użycia dynamicznego.
źródło