Modyfikator volatile
nie należy do często używanych elementów języka C(++). Jeśli więc gdzieś się go napotka, ma się sporą szansę nie odgadnąć od razu, dlaczego został użyty. Można też samemu przeoczyć sytuacje, gdy należałoby z niego korzystać.
Teoretycznie to słowo kluczowe nie powinno w ogóle istnieć; jest ono “techniczną” częścią języka, związaną ze sposobem jego kompilacji – trochę tak, jak klauzula checked
z C# jest ściśle związana z metodami wykonywania obliczeń przez (ko)procesor. Wiemy jednak doskonale, że C++ wysokim poziomem abstrakcji nie grzeszy (już o C nie wspominając), więc obecność takich elementów nie powinna specjalnie dziwić.
volatile
jako modyfikator zastosowany do (typu) zmiennej sprawia, że będzie ona traktowana tak, jakby jej zawartość mogła się w każdej chwili zmienić. Inaczej mówiąc, kompilator nie powinien nigdy czynić jakichkolwiek założeń co do wartości takiej zmiennej, bo jej wartość może być w dowolnym momencie przez coś zmodyfikowana.
Tak to wygląda od strony specyfikacji. W praktyce volatile
stosuje się głównie w dwóch przypadkach:
volatile
można jednak często się spotkać w programach uniksowych, które funkcjonują w oparciu o sygnały. Typowym przykładem są serwery sieciowe, działające w pętli aż do momentu, gdy im się przerwie:
Zmienna z warunku jest wtedy deklarowana jako:
zaś w procedurze obsługi sygnału – która może być wywołana w dowolnym momencie w odpowiedzi na przyjście żądanego sygnału (zwykle SIGINT) – jest ona ustawiana na 1. Gdyby nie była volatile
, kompilator uznałby, że zawsze ma ona wartość zerową i “zoptymalizował” powyższą pętlę, zamieniając ją na nieskończoną.
volatile
korzysta się wtedy, chcemy w sposób zamierzony zapobiec pewnym optymalizacjom kodu. Zwykle chodzi tutaj o jakieś testowanie szybkości pewnych operacji, np. arytmetycznych. Powinno się wtedy wykonywać je na zmiennych opatrzonych modyfikatorem volatile
, żeby mieć pewność, że wszystkie działania się wykonają i żadne nie zostanie wyeliminowane w procesie kompilacji. Można oczywiście wątpić w zdolności optymalizacyjne kompilatora, ale jest całkiem możliwe, że – zgodnie ze sprawdzonym po wielokroć prawem Murphy’ego – włączą się one właśnie tam, gdzie akurat byśmy ich sobie nie życzyli :)Jeśli więc któryś z tych dwóch powyższych przypadków pasuje do naszej sytuacji, zapewne powinniśmy posłużyć się słowem kluczowym volatile
.
Ja znam jeszcze dwa zastosowania volatile:
– Kiedy nie chcemy, żeby kompilator poprzestawiał kolejność odwołań do pamięci. To podobno może przyspieszyć operacje takie jak wypełnianie zablokowanego vertex czy index bufora w Direct3D.
– Kiedy zmienna jest używana przez wiele wątków. Oczywiście wspólny swobodny dostęp do zmiennej często nie wystarcza do poprawnej synchronizacji, ale czasem się sprawdza. Funkcje typu InterlockedIncrement też operują na zmiennych volatile.
Reg: Możesz rozwinąć ten pierwszy punkt? Nie jestem pewien, jak miałoby to wyglądać. Powiedzmy że mamy zalockowany VB i wskaźnik v do jego pamięci, a następnie wypełniamy go:
Jak sądzę, w 1. przypadku należałoby uczynić volatile licznik pętli (żeby zapobiec odliczeniu jej od tyłu; dość częsta optymalizacja), a w 2. – zmienną v. Czy jest może jakiś zupełnie inny schemat wypełniania bufora, gdzie można zastosować volatile?
nie działa mi paczka z megatutorialem zawierajaca wszystkie dzialy i spis tresci. Mogłby ktos to sprawdzic? :D
juz wszystko oke :)
Według mojej wiedzy właśnie nie powinno się używać volatile gdy zmienna jest używana przez kilka wątków.
Musi być volatile, jeśli zmienna jest współdzielona przez kilka wątków i wykorzystana np. jak poniżej:
1 wątek:
while (!flaga)
{
Sleep(1)
}
2 wątek:
..coś tam coś tam
flaga = true;
Jeśli flaga nie będzie volatile, to jest ryzyko, że kompilator zmienną flaga potraktuje jako rejestrową i nigdy nie wychwyci jej zmiany przez wątek 2 – będzie wtedy pętla nieskończona.
Nie mam na myśli oczywiście, że zawsze zmienne współdzielone muszą mieć volatile, tylko wtedy kiedy jest ryzyko, że kompilator tak skompiluje kod, że zamiast czytać w pętli w kółko stan zmiennej (pamięć) to zrobi sobie skrót i będzie czytał rejestr.
Jeśli natomiast mamy w ustawieniach kompilatora wyłączoną opcję optymalizacji zmiennych do rejestrów to używanie volatile nie jest konieczne.
Zapomniałeś o systemach embedded, gdzie volatile jest używane na porządku dziennym. Jeśli np. użyjesz zmiennej globalnej w przerwaniu to potem w w innym miejscu programu jest wielce prawdopodobne, że nie odczytasz poprawnej wartości tej zmiennej (przy włączonej optymalizacji). Po za tym porównaj dwie sytuacje:
1.
int i;
for(i=0; i<100; i++) {}
2.
volatile int i;
for(i=0; i<100; i++) {}
Przy włączonej optymalizacji masz pewność, że druga pętla zostanie wykonana mimo, że jest pusta i nic w niej nie ma, a po pierwszej pętli nie będzie śladu w kodzie wynikowym.