Czytałem niektóre artykuły i odpowiedzi Stack Exchange na temat używania volatile
słowa kluczowego, aby uniemożliwić kompilatorowi stosowanie optymalizacji obiektów, które mogą się zmieniać w sposób, który nie może być określony przez kompilator.
Jeśli czytam z ADC (nazwijmy zmienną adcValue
) i deklaruję tę zmienną jako globalną, czy powinienem volatile
w tym przypadku użyć słowa kluczowego ?
Bez użycia
volatile
słowa kluczowego// Includes #include "adcDriver.h" // Global variables uint16_t adcValue; // Some code void readFromADC(void) { adcValue = readADC(); }
Za pomocą
volatile
słowa kluczowego// Includes #include "adcDriver.h" // Global variables volatile uint16_t adcValue; // Some code void readFromADC(void) { adcValue = readADC(); }
Zadaję to pytanie, ponieważ podczas debugowania nie widzę żadnej różnicy między obydwoma podejściami, chociaż najlepsze praktyki mówią, że w moim przypadku (zmienna globalna, która zmienia się bezpośrednio ze sprzętu), użycie volatile
jest obowiązkowe.
microcontroller
c
embedded
Pryda
źródło
źródło
if(x==1) x=1;
zapisie może być zoptymalizowany dla nietrwałychx
i nie może być zoptymalizowany, jeślix
jest niestabilny. OTOH, jeśli potrzebne są specjalne instrukcje, aby uzyskać dostęp do urządzeń zewnętrznych, to do ciebie należy ich dodanie (np. Jeśli trzeba zapisać zakres pamięci).Odpowiedzi:
Definicja
volatile
volatile
informuje kompilator, że wartość zmiennej może ulec zmianie bez wiedzy kompilatora. Dlatego kompilator nie może założyć, że wartość się nie zmieniła tylko dlatego, że program C wydaje się jej nie zmieniać.Z drugiej strony oznacza to, że wartość zmiennej może być wymagana (odczyt) w innym miejscu, o którym kompilator nie wie, dlatego musi upewnić się, że każde przypisanie do zmiennej jest faktycznie wykonywane jako operacja zapisu.
Przypadków użycia
volatile
jest wymagane, gdyEfekty
volatile
Kiedy deklarowana jest zmienna,
volatile
kompilator musi upewnić się, że każde przypisanie do niej w kodzie programu jest odzwierciedlone w rzeczywistej operacji zapisu, oraz że każdy odczyt w kodzie programu odczytuje wartość z (mmapped) pamięci.W przypadku zmiennych nieulotnych kompilator zakłada, że wie, czy / kiedy zmienia się wartość zmiennej, i może optymalizować kod na różne sposoby.
Po pierwsze, kompilator może zmniejszyć liczbę odczytów / zapisów w pamięci, zachowując wartość w rejestrach procesora.
Przykład:
Tutaj kompilator prawdopodobnie nawet nie przydzieli pamięci RAM dla
result
zmiennej i nigdy nie będzie przechowywać wartości pośrednich nigdzie poza rejestrem procesora.Jeśli
result
był niestabilny, każde wystąpienieresult
kodu C wymagałoby od kompilatora dostępu do pamięci RAM (lub portu I / O), co prowadziłoby do obniżenia wydajności.Po drugie, kompilator może zmienić kolejność operacji na nielotnych zmiennych w celu zwiększenia wydajności i / lub rozmiaru kodu. Prosty przykład:
można ponownie zamówić
co może zapisać instrukcję asemblera, ponieważ wartość
99
nie musi być ładowana dwukrotnie.Jeśli
a
,b
ic
były niestabilne kompilator musiałby emitują instrukcji, które przypisać wartości w takiej kolejności, jak podano w programie.Inny klasyczny przykład jest taki:
Gdyby w tym przypadku
signal
tak nie byłovolatile
, kompilator „pomyślałby”, żewhile( signal == 0 )
może to być nieskończona pętla (ponieważsignal
nigdy nie zostanie zmieniona przez kod wewnątrz pętli ) i może wygenerować odpowiednikRozważne postępowanie z
volatile
wartościamiJak wspomniano powyżej,
volatile
zmienna może wprowadzić karę wydajności, gdy jest uzyskiwana częściej niż jest to faktycznie wymagane. Aby złagodzić ten problem, można „nieulotną” wartość przez przypisanie do zmiennej nieulotnej, takiej jakMoże to być szczególnie korzystne w ISR, gdzie chcesz być jak najszybciej nie dostępu do tego samego sprzętu lub urządzeń pamięci kilka razy kiedy pan wie, że nie jest potrzebna, ponieważ wartość ta nie ulegnie zmianie w czasie, gdy Izrael jest uruchomiony. Jest to powszechne, gdy ISR jest „producentem” wartości zmiennej, jak
sysTickCount
w powyższym przykładzie. W AVR byłoby szczególnie bolesne, gdyby funkcja miaładoSysTick()
dostęp do tych samych czterech bajtów w pamięci (cztery instrukcje = 8 cykli procesora na dostępsysTickCount
) pięć lub sześć razy zamiast tylko dwa razy, ponieważ programista wie, że wartość nie będzie zostać zmieniony z innego kodu podczas jego / jejdoSysTick()
uruchomienia.Dzięki tej sztuczce robisz dokładnie to samo, co kompilator robi dla zmiennych nieulotnych, tj. Odczytujesz je z pamięci tylko wtedy, gdy jest to konieczne, przechowujesz wartość w rejestrze przez pewien czas i zapisujesz w pamięci tylko wtedy, gdy trzeba ; ale tym razem, ty lepiej niż kompilator wiedzieć, jeśli / kiedy odczytu / zapisu musi się zdarzyć, więc zwalnia kompilator od tego zadania optymalizacji i zrobić to samemu.
Ograniczenia
volatile
Dostęp bezatomowy
volatile
nie nie zapewniają dostęp do zmiennych atomowej multi-word. W takich przypadkach oprócz korzystania należy zapewnić wzajemne wykluczenie w inny sposóbvolatile
. W AVR możesz korzystaćATOMIC_BLOCK
z<util/atomic.h>
lub z prostychcli(); ... sei();
połączeń. Odpowiednie makra działają również jako bariera pamięci, co jest ważne, jeśli chodzi o kolejność dostępu:Realizacja zamówienia
volatile
nakłada ścisłe polecenie wykonania tylko w odniesieniu do innych zmiennych zmiennych. Oznacza to na przykład, żegwarantuje się najpierw przypisanie 1 do,
i
a następnie przypisanie 2 doj
. Nie ma jednak gwarancji, żea
zostaną one przypisane pomiędzy; kompilator może wykonać to przypisanie przed fragmentem kodu lub po nim, w zasadzie w dowolnym momencie do pierwszego (widocznego) odczytua
.Gdyby nie bariera pamięci wyżej wspomnianych makr, kompilator byłby w stanie dokonać translacji
do
lub
(Ze względu na kompletność muszę powiedzieć, że bariery pamięci, takie jak te sugerowane przez makra sei / cli, mogą faktycznie uniemożliwić użycie
volatile
, jeśli wszystkie dostępy zostaną uzupełnione tymi barierami.)źródło
An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects.
Więcej osób powinno ją przeczytać.cli
/sei
jest zbyt ciężkim rozwiązaniem, jeśli Twoim jedynym celem jest osiągnięcie bariery pamięci, a nie zapobieganie przerwom. Te makra generują rzeczywistecli
/sei
instrukcje i dodatkowo pamięć clobber, i to właśnie takie clobbering powoduje powstanie bariery. Aby mieć tylko barierę pamięci bez wyłączania przerwań, możesz zdefiniować własne makro z ciałem podobnym__asm__ __volatile__("":::"memory")
(tj. Pusty kod asemblera z clobberem pamięci).volatile
, jest punkt sekwencji, a wszystko po nim musi być „zsekwencjonowane po”. Oznacza to, że wyrażenie jest swoistą barierą pamięci. Dostawcy kompilatorów postanowili rozpowszechniać wszelkiego rodzaju mity, aby na programistach spoczywać odpowiedzialność za bariery pamięci, ale to narusza zasady „abstrakcyjnej maszyny”.volatile data_t data = {0}; set_mmio(&data); while (!data.ready);
.Zmienne słowo kluczowe informuje kompilator, że dostęp do zmiennej ma zauważalny efekt. Oznacza to, że za każdym razem, gdy kod źródłowy używa zmiennej, kompilator MUSI utworzyć dostęp do zmiennej. Czy to dostęp do odczytu lub zapisu.
Skutkuje to tym, że każda zmiana zmiennej poza normalnym przepływem kodu będzie również obserwowana przez kod. Np. Jeśli program obsługi przerwań zmienia wartość. Lub jeśli zmienna jest w rzeczywistości jakimś rejestrem sprzętowym, który zmienia się sam.
Ta wielka korzyść jest również jej wadą. Każdy pojedynczy dostęp do zmiennej przechodzi przez zmienną, a wartość nigdy nie jest przechowywana w rejestrze, co zapewnia szybszy dostęp przez dowolny czas. Oznacza to, że zmienna zmienna będzie wolna. Wielkość wolniej. Dlatego używaj lotnych tylko wtedy, gdy jest to rzeczywiście konieczne.
W twoim przypadku, o ile pokazałeś kod, zmienna globalna jest zmieniana tylko wtedy, gdy sam ją zaktualizujesz
adcValue = readADC();
. Kompilator wie, kiedy to się dzieje, i nigdy nie będzie przechowywał wartości adcValue w rejestrze nad czymś, co może wywołaćreadFromADC()
funkcję. Lub jakiejkolwiek funkcji, o której nie wie. Lub cokolwiek, co zmanipuluje wskaźniki, które mogą wskazywaćadcValue
i tak dalej. Naprawdę nie ma potrzeby niestabilności, ponieważ zmienna nigdy nie zmienia się w nieprzewidywalny sposób.źródło
volatile
wszystkiego tylko dlatego , że , ale nie powinieneś również unikać tego w przypadkach, w których uważasz, że jest to słusznie wymagane ze względu na zapobiegawcze obawy o wydajność.Głównym zastosowaniem lotnego słowa kluczowego we wbudowanych aplikacjach C jest oznaczenie zmiennej globalnej zapisywanej w module obsługi przerwań. W tym przypadku z pewnością nie jest to opcjonalne.
Bez tego kompilator nie może udowodnić, że wartość jest zawsze zapisywana po inicjalizacji, ponieważ nie może udowodnić, że wywoływana jest funkcja obsługi przerwań. Dlatego uważa, że może zoptymalizować zmienną nieistniejącą.
źródło
Istnieją dwa przypadki, w których należy użyć
volatile
w systemach wbudowanych.Podczas odczytu z rejestru sprzętu.
Oznacza to, że sam rejestr odwzorowany w pamięci, część sprzętowych urządzeń peryferyjnych wewnątrz MCU. Prawdopodobnie będzie miał jakąś tajemniczą nazwę, taką jak „ADC0DR”. Rejestr ten musi być zdefiniowany w kodzie C, albo poprzez mapę rejestrów dostarczoną przez dostawcę narzędzia, albo przez ciebie. Aby zrobić to sam, zrobiłbyś (zakładając rejestr 16-bitowy):
gdzie 0x1234 to adres, na którym MCU zamapował rejestr. Ponieważ
volatile
jest już częścią powyższego makra, każdy dostęp do niego będzie miał zmienną kwalifikację. Więc ten kod jest w porządku:Podczas udostępniania zmiennej między ISR a powiązanym kodem przy użyciu wyniku ISR.
Jeśli masz coś takiego:
Następnie kompilator może pomyśleć: „adc_data ma zawsze wartość 0, ponieważ nie jest nigdzie aktualizowane. I ta funkcja ADC0_interrupt () nigdy nie jest wywoływana, więc zmiennej nie można zmienić”. Kompilator zwykle nie zdaje sobie sprawy, że przerwania wywoływane są przez sprzęt, a nie przez oprogramowanie. Tak więc kompilator usuwa i usuwa kod,
if(adc_data > 0){ do_stuff(adc_data); }
ponieważ uważa, że nigdy nie będzie to prawdą, powodując bardzo dziwny i trudny do debugowania błąd.Deklarując
adc_data
volatile
, kompilator nie może przyjmować takich założeń i nie może optymalizować dostępu do zmiennej.Ważne notatki:
ISR zawsze deklaruje się w sterowniku sprzętowym. W takim przypadku ADC ISR powinien znajdować się w sterowniku ADC. Nikt inny oprócz kierowcy nie powinien komunikować się z ISR - wszystko inne to programowanie spaghetti.
Podczas pisania C wszelka komunikacja między ISR a programem działającym w tle musi być chroniona przed warunkami wyścigowymi. Zawsze za każdym razem bez wyjątków. Rozmiar magistrali danych MCU nie ma znaczenia, ponieważ nawet jeśli wykonasz pojedynczą 8-bitową kopię w C, język nie może zagwarantować atomowości operacji. Nie, chyba że użyjesz funkcji C11
_Atomic
. Jeśli ta funkcja nie jest dostępna, musisz użyć jakiegoś semafora lub wyłączyć przerwanie podczas odczytu itp. Inline asembler to kolejna opcja.volatile
nie gwarantuje atomowości.To, co może się zdarzyć, to: -
Załaduj wartość ze stosu do rejestru -
Wystąpienie przerwania - Zastosuj
wartość z rejestru
I wtedy nie ma znaczenia, czy część „użyj wartości” jest sama w sobie instrukcją. Niestety, znaczna część wszystkich programistów systemów wbudowanych jest tego nieświadoma, co prawdopodobnie czyni go najczęstszym błędem systemów wbudowanych w historii. Zawsze przerywany, trudny do sprowokowania, trudny do znalezienia.
Przykład poprawnie napisanego sterownika ADC wyglądałby tak (zakładając, że C11
_Atomic
jest niedostępny):adc.h
adc.c
Ten kod zakłada, że samo przerwanie nie może być przerwane. W takich systemach prosty boolean może działać jako semafor i nie musi być atomowy, ponieważ nie ma żadnej szkody, jeśli przerwanie nastąpi przed ustawieniem boolean. Wadą powyższej uproszczonej metody jest to, że odrzuci ona odczyty ADC, gdy wystąpią warunki wyścigu, używając poprzedniej wartości. Można tego również uniknąć, ale kod staje się bardziej złożony.
Tutaj
volatile
chroni przed błędami optymalizacji. Nie ma to nic wspólnego z danymi pochodzącymi z rejestru sprzętowego, tylko że dane są udostępniane ISR.static
chroni przed programowaniem spaghetti i zanieczyszczeniem przestrzeni nazw, ustawiając zmienną lokalnie dla kierowcy. (Jest to dobre w aplikacjach jedno-rdzeniowych, jednowątkowych, ale nie w aplikacjach wielowątkowych.)źródło
semaphore
zdecydowanie powinien byćvolatile
! W rzeczywistości, to najbardziej podstawowy przypadek użycia wich wymaga : coś sygnału z jednego kontekstu wykonania do drugiego. - W twoim przykładzie kompilator może po prostu pominąć, ponieważ „widzi”, że jego wartość nigdy nie jest czytana, zanim zostanie zastąpiona przez .volatile
semaphore = true;
semaphore = false;
We fragmentach kodu przedstawionych w pytaniu nie ma jeszcze powodu, aby używać zmiennej. Nie ma znaczenia, że wartość
adcValue
pochodzi z ADC. AadcValue
bycie globalnym powinno wzbudzać podejrzenia co doadcValue
niestabilności, ale nie jest to sam w sobie powód.Bycie globalnym jest wskazówką, ponieważ otwiera możliwość, do której
adcValue
można uzyskać dostęp z więcej niż jednego kontekstu programu. Kontekst programu obejmuje moduł obsługi przerwań i zadanie RTOS. Jeśli zmienna globalna zostanie zmieniona o jeden kontekst, wówczas inne konteksty programu nie mogą założyć, że znają wartość z poprzedniego dostępu. Każdy kontekst musi ponownie odczytać wartość zmiennej za każdym razem, gdy z niej korzysta, ponieważ wartość mogła zostać zmieniona w innym kontekście programu. Kontekst programu nie jest świadomy, kiedy nastąpi przerwanie lub zmiana zadania, dlatego należy założyć, że wszelkie zmienne globalne używane przez wiele kontekstów mogą zmieniać się między dostępami do zmiennej z powodu możliwego przełączenia kontekstu. Do tego służy zmienna deklaracja. Mówi kompilatorowi, że ta zmienna może się zmieniać poza kontekstem, więc czytaj ją przy każdym dostępie i nie zakładaj, że znasz już wartość.Jeśli zmienna jest odwzorowana w pamięci na adres sprzętowy, to zmiany dokonane przez sprzęt są faktycznie innym kontekstem poza kontekstem twojego programu. Odwzorowanie pamięci jest również wskazówką. Na przykład, jeśli twoja
readADC()
funkcja uzyskuje dostęp do wartości odwzorowanej w pamięci, aby uzyskać wartość ADC, to zmienna odwzorowana w pamięci powinna być prawdopodobnie niestabilna.Wracając do pytania, jeśli kod zawiera coś więcej i
adcValue
dostęp do niego zapewnia inny kod działający w innym kontekście, to tak,adcValue
powinien być zmienny.źródło
To, że wartość pochodzi z jakiegoś sprzętowego rejestru ADC, nie oznacza, że jest on „bezpośrednio” zmieniany przez sprzęt.
W twoim przykładzie po prostu wywołujesz readADC (), która zwraca pewną wartość rejestru ADC. Jest to w porządku w odniesieniu do kompilatora, wiedząc, że adcValue ma w tym momencie nową wartość.
Byłoby inaczej, gdybyś używał procedury przerwania ADC do przypisania nowej wartości, która jest wywoływana, gdy nowa wartość ADC jest gotowa. W takim przypadku kompilator nie miałby pojęcia, kiedy wywoływany jest odpowiedni ISR i może zdecydować, że adcValue nie będzie dostępny w ten sposób. W tym miejscu pomogłaby niestabilność.
źródło
Zachowanie
volatile
argumentu zależy w dużej mierze od kodu, kompilatora i wykonanej optymalizacji.Istnieją dwa przypadki użycia, w których osobiście korzystam
volatile
:Jeśli istnieje zmienna, na którą chcę spojrzeć za pomocą debuggera, ale kompilator ją zoptymalizował (oznacza, że ją usunął, ponieważ okazało się, że ta zmienna nie jest konieczna), dodanie
volatile
zmusi kompilator do jej zachowania, a zatem można zobaczyć podczas debugowania.Jeśli zmienna może zmienić się „poza kodem”, zazwyczaj, jeśli masz do niej dostęp sprzętowy lub mapujesz zmienną bezpośrednio na adres.
Wbudowane są też czasem pewne błędy w kompilatorach, które optymalizują, które faktycznie nie działają, a czasem
volatile
mogą rozwiązać problemy.Biorąc pod uwagę, że twoja zmienna jest zadeklarowana globalnie, prawdopodobnie nie zostanie zoptymalizowana, dopóki zmienna jest używana w kodzie, przynajmniej zapisywana i odczytywana.
Przykład:
W takim przypadku zmienna prawdopodobnie zostanie zoptymalizowana do printf („% i”, 1);
nie będzie zoptymalizowany
Inny:
W takim przypadku kompilator może zoptymalizować (jeśli zoptymalizujesz pod kątem prędkości), a tym samym odrzucić zmienną
W twoim przypadku „może to zależeć” od reszty kodu, sposobu
adcValue
użycia w innym miejscu oraz używanych wersji / ustawień optymalizacji kompilatora.Czasami posiadanie kodu, który działa bez optymalizacji, może być denerwujące, ale ulega awarii po zoptymalizowaniu.
Można to zoptymalizować do printf („% i”, readADC ());
-
Prawdopodobnie nie zostaną one zoptymalizowane, ale nigdy nie wiadomo „jak dobry jest kompilator” i mogą ulec zmianie wraz z parametrami kompilatora. Zwykle kompilatory z dobrą optymalizacją są licencjonowane.
źródło
volatile
zmusza kompilator do przechowywania zmiennej w pamięci RAM i do aktualizacji tej pamięci RAM, gdy tylko wartość zostanie przypisana do zmiennej. Przez większość czasu kompilator nie „usuwa” zmiennych, ponieważ zwykle nie piszemy przypisań bez efektu, ale może zdecydować o zachowaniu zmiennej w jakimś rejestrze procesora i może później lub nigdy nie zapisać wartości tego rejestru do pamięci RAM. Debugery często nie potrafią zlokalizować rejestru procesora, w którym przechowywana jest zmienna, a zatem nie mogą pokazać jej wartości.Wiele technicznych wyjaśnień, ale chcę skoncentrować się na praktycznym zastosowaniu.
Słowo
volatile
kluczowe zmusza kompilator do odczytu lub zapisu wartości zmiennej z pamięci za każdym razem, gdy jest używana. Zwykle kompilator będzie próbował zoptymalizować, ale nie robić niepotrzebnych odczytów i zapisów, np. Utrzymując wartość w rejestrze procesora, a nie uzyskując dostęp do pamięci za każdym razem.Ma to dwa główne zastosowania w kodzie osadzonym. Po pierwsze służy do rejestrów sprzętowych. Rejestry sprzętowe mogą się zmieniać, np. Rejestr wyników ADC może być zapisywany przez urządzenie peryferyjne ADC. Rejestry sprzętowe mogą także wykonywać akcje po uzyskaniu dostępu. Typowym przykładem jest rejestr danych UART, który często usuwa flagi przerwań podczas odczytu.
Kompilator zwykle próbuje zoptymalizować powtarzane odczyty i zapisy rejestru przy założeniu, że wartość nigdy się nie zmieni, więc nie ma potrzeby dalszego dostępu do niej, ale
volatile
słowo kluczowe zmusi ją do wykonania operacji odczytu za każdym razem.Drugim powszechnym zastosowaniem są zmienne używane zarówno przez kod przerwań, jak i bez przerwania. Przerwania nie są wywoływane bezpośrednio, więc kompilator nie może określić, kiedy będą wykonywane, i dlatego zakłada, że żadne dostępy w przerwaniu nigdy się nie zdarzają. Ponieważ
volatile
słowo kluczowe wymusza na kompilatorze dostęp do zmiennej za każdym razem, założenie to jest usuwane.Należy zauważyć, że
volatile
słowo kluczowe nie jest kompletnym rozwiązaniem tych problemów i należy dołożyć starań, aby ich uniknąć. Na przykład w systemie 8-bitowym zmienna 16-bitowa wymaga dwóch dostępów do pamięci do odczytu lub zapisu, a zatem nawet jeśli kompilator jest zmuszony do uzyskania dostępu, następuje sekwencyjnie, a sprzęt może działać przy pierwszym dostępie lub przerwa między nimi.źródło
W przypadku braku
volatile
kwalifikatora wartość obiektu może być przechowywana w więcej niż jednym miejscu podczas niektórych części kodu. Rozważmy na przykład, biorąc pod uwagę coś takiego:Na początku C kompilator przetworzyłby instrukcję
poprzez kroki:
Bardziej wyrafinowane kompilatory rozpoznają jednak, że jeśli wartość „foo” jest przechowywana w rejestrze podczas pętli, trzeba będzie ją załadować tylko raz przed pętlą i zapisać raz później. Jednak podczas pętli będzie to oznaczać, że wartość „foo” jest przechowywana w dwóch miejscach - w pamięci globalnej i w rejestrze. Nie będzie to stanowić problemu, jeśli kompilator będzie widział wszystkie sposoby dostępu do „foo” w pętli, ale może powodować problemy, jeśli wartość „foo” jest dostępna w jakimś mechanizmie, o którym kompilator nie wie ( takich jak moduł obsługi przerwań).
Być może autorzy Standardu mogliby dodać nowy kwalifikator, który wyraźnie zachęciłby kompilator do dokonania takich optymalizacji i powiedziałby, że staromodna semantyka miałaby zastosowanie w przypadku jej braku, ale przypadki, w których optymalizacje są użyteczne, znacznie przewyższają liczbę te, w których byłoby to problematyczne, więc Standard zamiast tego pozwala kompilatorom założyć, że takie optymalizacje są bezpieczne przy braku dowodów, że tak nie jest. Celem tego
volatile
słowa kluczowego jest dostarczenie takich dowodów.Kilka sytuacji spornych między niektórymi autorami kompilatorów i programistami występuje w sytuacjach takich jak:
Historycznie większość kompilatorów albo dopuszczałaby możliwość, że zapisanie miejsca w
volatile
pamięci mogłoby wywołać dowolne skutki uboczne i uniknąć buforowania jakichkolwiek wartości w rejestrach w takim sklepie, albo powstrzymałyby się od buforowania wartości w rejestrach między wywołaniami funkcji, które są nie kwalifikuje się jako „wbudowany”, a zatem zapisuje 0x1234output_buffer[0]
, ustawia dane wyjściowe, poczekaj na zakończenie, a następnie napisz 0x2345output_buffer[0]
i kontynuuj od tego momentu . Standard nie wymaga implementacji, aby traktować akt przechowywania adresuoutput_buffer
dovolatile
-kwalifikował wskaźnik jako znak, że coś może się z tym wydarzyć, co oznacza, że kompilator nie rozumie, ponieważ autorzy sądzili, że kompilator pisarzy kompilatorów przeznaczonych dla różnych platform i celów rozpozna, gdy to zrobi, będzie służyć tym celom na tych platformach bez konieczności mówienia. W związku z tym niektóre „sprytne” kompilatory, takie jak gcc i clang, przyjmą, że nawet jeśli adresoutput_buffer
jest zapisany w zmiennym wskaźniku między dwoma sklepamioutput_buffer[0]
, nie ma powodu zakładać, że cokolwiek może obchodzić wartość przechowywana w tym obiekcie w ten czas.Ponadto, podczas gdy wskaźniki, które są rzutowane bezpośrednio z liczb całkowitych, rzadko są używane do celów innych niż manipulowanie rzeczami w sposób, którego kompilatory raczej nie zrozumieją, Standard ponownie nie wymaga kompilatorów do traktowania takich dostępów jak
volatile
. W konsekwencji pierwszy zapis do*((unsigned short*)0xC0001234)
może zostać pominięty przez „sprytne” kompilatory, takie jak gcc i clang, ponieważ opiekunowie takich kompilatorów wolą twierdzić, że kod, który nie kwalifikuje takich rzeczy jakovolatile
„zepsuty”, niż uznają, że przydatność takiego kodu jest przydatna . Wiele plików nagłówkowych dostarczonych przez dostawcę pomijavolatile
kwalifikatory, a kompilator zgodny z plikami nagłówkowymi dostarczonymi przez dostawcę jest bardziej przydatny niż ten, który nie jest.źródło