volatile?…

2009-02-23 13:18

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:

  • Po pierwsze, używamy tego słowa jeśli rzeczywiście wartość naszej zmiennej może być zmodyfikowana przez jakiś ‘czynnik zewnętrzny’. Na niektórych maszynach tak na przykład działają urządzenia wejściowe: akcja w rodzaju wciśnięcia klawisza przez użytkownika powoduje zmianę zawartości jakiejś komórki pamięci. Chcąc na nią zareagować, należy więc cały czas monitorować to miejsce, bo może ono zmienić swoją zawartość zupełnie niespodziewanie. Oczywiście teraz takich rozwiązań się już nie stosuje, bo urządzenia wejściowe generują po prostu przerwania (interrupts), które są potem przerabiane przez system operacyjny na odpowiednie zdarzenia.
    Z globalnymi zmiennymi 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:

    1. while (!interrupted) do_work();

    Zmienna z warunku jest wtedy deklarowana jako:

    1. volatile sig_atomic_t interrupted = 0;

    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ą.

  • Po drugie, z 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.

Tags: ,
Author: Xion, posted under Programming »


7 comments for post “volatile?…”.
  1. Reg:
    March 1st, 2009 o 11:07

    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.

  2. Xion:
    March 6th, 2009 o 1:22

    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:

    1. // 1. sposób
    2. for (int i = 0; i < n; ++i)
    3. {
    4.    v&#91;i].diffuse = 0xff000000;
    5.    // itd.
    6. }
    7.  
    8. // 2. sposób
    9. VERTEX* v_end = v + n;
    10. while (v < v_end)
    11. {
    12.     v->diffuse = 0xff000000;
    13.     ++v;
    14. }

    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?

  3. aqq:
    March 7th, 2009 o 14:40

    nie działa mi paczka z megatutorialem zawierajaca wszystkie dzialy i spis tresci. Mogłby ktos to sprawdzic? :D

  4. aqq:
    March 9th, 2009 o 13:13

    juz wszystko oke :)

  5. cookie_m:
    May 6th, 2009 o 19:04

    Według mojej wiedzy właśnie nie powinno się używać volatile gdy zmienna jest używana przez kilka wątków.

  6. Marcin Sikorski:
    October 23rd, 2009 o 13:41

    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.

  7. Olek:
    November 13th, 2009 o 9:46

    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.

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.