Są do tego inne mechanizmy. Kompilator nawet nie ma takiej możliwości, bo volatile nie oznacza nic innego niż "nie optymalizuj dostępu do tej zmiennej". Kompilator może, ale nie musi wiedzieć, co zmienia tę zmienną.
Zobacz, jak w AVR są zrealizowane rejestry 16-bitowe (np. datasheet do atmega8 strona 77, "Accessing 16-bit Registers").
A to " (czyli że wyłączy wszystko inne, co może zmienić jej >>> wartość w trakcie dostępu -- czy to wątki, czy przerwania, czy zewnętrzny >>> sprzęt)."
nic takiego kompilator nie robi. Przerwania są wyłączane tylko w atomic blok.
Janusz <janusz snipped-for-privacy@o2.pl napisał(a):
A to "`volatile` nie oznacza, że kompilator gwarantuje atomiczny dostęp do zmiennej (czyli że wyłączy"? Nie dość, że masz problem z czytaniem ze zrozumieniem, to jeszcze wyciąłeś kluczowy fragment, który pokazuje, że czepiasz się bez sensu.
Cos w tym jest, ale z drugiej strony - skoro uzywamy volatile, to wiadomo ze zmienna moze sie zmieniac w przerwaniach czy w inny niekontrolowany sposob, i co - kompilator to olewa ?
No coz, przejsc na ARM i zapomniec o problemie ... na chwile, bo i tam sie moga podobne cuda zrobic, jak zmiennych wiecej :-)
Nie olewa przecież, bo przestaje względem niej wykonywać optymalizacje. Natomiast nie robi nadgorliwych przeróbek, które spowodowałyby niekontrolowane zachowanie programu. Naprawdę chciałbyś żeby kompilator gdzieś po cichu włączał albo wyłączał Ci przerwania?
Ale co kompilator może zrobić, jak sam procesor nie obsługuje atomicznego dostępu do tej zmiennej (bo np. jest 8-bitowy, a zmienna 16-bitowa)? Zresztą nawet w przypadku zmiennych o rozmiarze równym szerokości magistrali danych nie ma gwarancji chociażby w przypadku operacji read-modify-write. Przykład:
#v+ test.s ldr r3, [r2] ; odczyt zmiennej z pamięci do rejestru r3 add r3, r3, #1 ; dodanie do rejestru r3 liczby 1 str r3, [r2] ; zapis rejestru r3 z powrotem do pamięci #v-
Zmienna może się zmienić między każdą z tych instrukcji.
Bardziej realny przykład, AVR. Chcemy zmienić stan bitu 2 w porcie na przeciwny.
#v+ avr.c #include <avr/io.h>
void fn(void) { PORTB ^= _BV(2); } #v-
Kompilacja: avr-gcc -mmcu=atmega8 -O2 -S avr.c
#v+ avr.s in r24,0x18 ; załadowanie adresu portu do r24 ldi r25,lo8(4) ; załadowanie wartości _BV(2) do r25 eor r24,r25 ; wykonanie r24 xor r25, zapis do r24 out 0x18,r24 ; wysłanie wartości z r24 do portu #v-
Między każdą z tych instrukcji również może wystąpić przerwanie, które np. ustawi inny bit w tym porcie, który to bit zostanie ładnie wyczyszczony podczas otatniej operacji (zapisu r24 do portu).
Czemu? Co ma do tego ARM? Chodzi o szerokość magistrali danych? To rozwiązuje tylko jeden problem, ale inne (chociażby ten pierwszy przykład wyżej) pozostają.
int main(void) { signal(SIGALRM, sighnd); alarm(2);
printf("Czekanie na sygnal\n"); while (!g_signo) ; printf("Odebrano sygnal\n");
return 0; } #v-
Kod bez optymalizacji (gcc -O0) widzi zmianę w g_signo. Kod z -O1 i -O2 nie widzi. Dodanie `volatile` do definicji zmiennej sprawia, że kod nawet z optymalizacjami widzi zmianę.
Użytkownik "Grzegorz Niemirowski" napisał w wiadomości grup dyskusyjnych:q43h41$kh6$ snipped-for-privacy@node1.news.atman.pl... J.F. <jfox snipped-for-privacy@poczta.onet.pl> napisał(a):
Na kilka instrukcji ... czemu nie.
Szczegolnie, ze ... sam musze je wylaczyc, jesli nie chce takich niespodzianek. To co przyniesie wiecej szkody - jak kompilator bedzie je wylaczal automatycznie, czy jak ja zapomne ? :-)
Natomiast na niektorych procesorach moze byc problem z odtworzeniem stanu przerwan, no i kwestia robienia tego w procedurze obslugi przerwania czy zagniezdzania przerwan.
Których instrukcji? Wszystkich odwołujących się do tej zmiennej? Nadmierna, ukryta ingerencja w kod. Efektem będzie np. niepożądany jitter. Kod robi się mniej przewidywalny.
Albo przepisać tak, żeby wyłączanie nie było konieczne.
Zdecydowanie jak kompilator będzie wyłączał automatycznie. Ty sobie przypomnisz i będzie OK. A miliony programistów będą się męczyć z dziwnym, nieprzewidywalnym zachowaniem kompilatora, który próbuje być mądrzejszy od nich.
Januszowi wlasnie chodzilo o to ze nie: 'volatile' w intencji sluzy do obsugi sprzetu: jak np. urzadzenie zlicza ile razy byl zrobiony zapis to wynik ma byc taki jak wynika jak napisal programista, nic mniej, nic wiecej. Sprzet zwykle nie wymaga atomicznego zapisu wiec 'volatile' nie daje takiej gwarancji. 'volatile' moze tez byc uzyte do innych celow, dlatego standard zawiera bardzo ogolne sformulowanie. Ale _nie_ ma zadnej gwarancji atomicznosci.
Programista ma kontekst, którego nie ma kompilator. Programista wie, kiedy chce mieć sekcję krytyczną (która nie musi obejmować wyłącznie atomicznego dostępu do zmiennej szerszej niż magistrala adresowa).
Jest jest... po to są sekcje krytyczne.
Z odczytem int nie, ale np. z read-modify-write już tak.
A to swoją drogą... ale raczej przez ludzi a nie przez komputery :)
Trzeba po prostu pamiętać, że (przynajmniej w przypadku C i C++) kompilator nie tłumaczy kodu na język maszynowy jeden do jednego. Kod to tylko pewien abstrakcyjny opis, który kompilator może traktować z dosyć dużą dowolnością. Dochodzą do tego chociażby zagadnienia związane z reorderingiem.
Przykładowo:
formatting link
Dzielimy (powolna operacja)
Wyłączamy przerwania cli
Zapisujemy wynik dzielenia do zmiennej (szybka operacja)
Włączamy przerwania
A optymalizator twierdzi, że wyłączy sobie przerwania przed dzieleniem, bo tak mu mnieszy kod wychodzi :)
Gdy zagłębimy się w optymalizowanie "undefined behavior", to robi się jeszcze ciekawiej. Przykładowo:
#v+ void fn(int *p) { int a = *p; // martwy kod if (p == 0) return; // nadmiarowe sprawdzenie *p = 1; } #v-
Spodziewalibyśmy się, że `if (p == 0) return;` nie zostanie usunięte, ale ponieważ programista wcześniej dokonał dereferencji, a dereferencja null pointera jest UB, to kompilator może usunąć ten drugi, nadmiarowy test bo zakłada, że nie ma UB (czyli że p nie może być 0). Moje gcc nie usuwa, bo najpierw usuwa martwy kod, ale wcale nie musi -- inna wersja lub inny kompilator może zachowywać się inaczej i najpierw usunąć nadmiarowe sprawdzenie, a dopiero potem martwy kod.
Kolejny przykład.
#v+ #include <limits.h>
#include <stdio.h>
int fn(int i) { if (i + 1 > i) printf("Nie ma integer overflow\n"); else printf("Uwaga: integer overflow\n"); }
int main(void) { fn(INT_MAX); return 0; } #v-
Kompilujemy z -O0, dostajemy wynik, że jest integer overflow. Kompilujemy z -O2, optymalizator stwierdza, że przepełnienie typu ze znakiem jest UB, więc usuwa nam ten warunek (ustawia go na true).
W C# pisałem coś tylko raz i tylko dlatego, że musiałem :) Zupełnie nie moja działka.
ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here.
All logos and trade names are the property of their respective owners.