Perché esiste la volatilità?


222

Cosa fa la volatileparola chiave? In C ++ che problema risolve?

Nel mio caso, non ne ho mai avuto consapevolmente bisogno.


Ecco un'interessante discussione sulla volatilità per quanto riguarda il modello Singleton: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
chessguy,

3
Esiste una tecnica intrigante che consente al compilatore di rilevare possibili condizioni di gara che si basano pesantemente sulla parola chiave volatile, che puoi leggere su http://www.ddj.com/cpp/184403766 .
Neno Ganchev,

Questa è una bella risorsa con un esempio di quando volatilepuò essere utilizzata in modo efficace, messa insieme in termini piuttosto profani. Link: publications.gbdirect.co.uk/c_book/chapter8/…
Ottimizzatore programmato

Lo uso per bloccare il codice libero / doppio controllo controllato
paulm

Per me, volatilepiù utile della friendparola chiave.
acegs,

Risposte:


268

volatile è necessario se stai leggendo da un punto della memoria che, per esempio, un processo / dispositivo completamente separato / qualunque cosa possa scrivere.

Lavoravo con ram a doppia porta in un sistema multiprocessore in scala C. Abbiamo usato un semaforo con un valore di 16 bit gestito dall'hardware per sapere quando l'altro era finito. Essenzialmente abbiamo fatto questo:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Senza volatile, l'ottimizzatore vede il ciclo come inutile (il ragazzo non imposta mai il valore! È pazzo, sbarazzarsi di quel codice!) E il mio codice procederebbe senza aver acquisito il semaforo, causando problemi in seguito.


In questo caso, cosa accadrebbe se uint16_t* volatile semPtrinvece fosse scritto? Ciò dovrebbe contrassegnare il puntatore come volatile (anziché il valore indicato), in modo che i controlli sul puntatore stesso, ad es. semPtr == SOME_ADDRPotrebbero non essere ottimizzati. Ciò implica tuttavia anche un valore puntato volatile. No?
Zyl,

@Zyl No, non lo è. In pratica, ciò che suggerisci è probabilmente ciò che accadrà. Ma teoricamente, si potrebbe finire con un compilatore che ottimizza l'accesso ai valori perché ha deciso che nessuno di questi valori è mai stato modificato. E se intendevi applicare volatile al valore e non al puntatore, verrai fregato. Ancora una volta, è improbabile, ma è meglio sbagliare nel fare le cose bene, piuttosto che trarre vantaggio dal comportamento che succede oggi.
iheanyi,

1
@Doug T. Una spiegazione migliore è questa
machineaddict

3
@curiousguy non ha deciso male. Ha fatto la detrazione corretta in base alle informazioni fornite. Se non si riesce a contrassegnare qualcosa di volatile, il compilatore è libero di presumere che non sia volatile . Ecco cosa fa il compilatore quando ottimizza il codice. Se vi sono ulteriori informazioni, vale a dire che tali dati sono in effetti volatili, è responsabilità del programmatore fornire tali informazioni. Quello che stai sostenendo da un compilatore buggy è davvero solo una cattiva programmazione.
iheanyi,

1
@curiousguy no, solo perché la parola chiave volatile appare una volta non significa che tutto diventa improvvisamente volatile. Ho fornito uno scenario in cui il compilatore fa la cosa giusta e ottiene un risultato contrario a ciò che il programmatore si aspetta erroneamente. Proprio come "l'analisi più irritante" non è il segno dell'errore del compilatore, né è il caso qui.
iheanyi,

82

volatileè necessario quando si sviluppano sistemi embedded o driver di dispositivo, in cui è necessario leggere o scrivere un dispositivo hardware mappato in memoria. Il contenuto di un particolare registro del dispositivo può cambiare in qualsiasi momento, quindi è necessario la volatileparola chiave per assicurarsi che tali accessi non siano ottimizzati dal compilatore.


9
Questo non è valido solo per i sistemi embedded ma per lo sviluppo di tutti i driver di dispositivo.
Mladen Janković,

L'unica volta in cui ne ho mai avuto bisogno su un bus ISA a 8 bit in cui hai letto lo stesso indirizzo due volte: il compilatore aveva un bug e lo ignorava (primi Zortech c ++)
Martin Beckett,

Molto volatile è raramente adeguato per il controllo di dispositivi esterni. La sua semantica è sbagliata per il moderno MMIO: devi rendere volatili troppi oggetti e fa male all'ottimizzazione. Ma il moderno MMIO si comporta come una normale memoria fino a quando non viene impostato un flag così volatile. Molti driver non usano mai volatile.
curioso

69

Alcuni processori hanno registri a virgola mobile che hanno più di 64 bit di precisione (es. X86 a 32 bit senza SSE, vedi il commento di Peter). In questo modo, se si eseguono diverse operazioni su numeri a doppia precisione, si ottiene effettivamente una risposta di precisione superiore rispetto a se si dovesse troncare ogni risultato intermedio a 64 bit.

Questo di solito è ottimo, ma significa che, a seconda di come il compilatore ha assegnato i registri e le ottimizzazioni, otterrai risultati diversi per le stesse identiche operazioni sugli stessi input. Se hai bisogno di coerenza, puoi forzare ogni operazione a tornare in memoria usando la parola chiave volatile.

È anche utile per alcuni algoritmi che non hanno alcun senso algebrico ma riducono l'errore in virgola mobile, come la somma di Kahan. Algebricamente è un nop, quindi spesso viene ottimizzato in modo errato a meno che alcune variabili intermedie non siano volatili.


5
Quando si calcolano le derivate numeriche, è utile anche assicurarsi che x + h - x == h definisca hh = x + h - x come volatile in modo da poter calcolare un delta corretto.
Alexandre C.,

5
+1, in effetti nella mia esperienza c'è stato un caso in cui i calcoli in virgola mobile hanno prodotto risultati diversi in Debug e Release, quindi i test unitari scritti per una configurazione stavano fallendo per un'altra. L'abbiamo risolto dichiarando una variabile in virgola mobile come volatile doubleanziché solo double, in modo da garantire che venisse troncata dalla precisione FPU alla precisione a 64 bit (RAM) prima di continuare ulteriori calcoli. I risultati furono sostanzialmente diversi a causa di un'ulteriore esagerazione dell'errore in virgola mobile.
Serge Rogatch,

La tua definizione di "moderno" è un po 'fuori. Solo il codice x86 a 32 bit che evita SSE / SSE2 è interessato da questo, e non era "moderno" nemmeno 10 anni fa. MIPS / ARM / POWER hanno tutti i registri hardware a 64 bit, così come x86 con SSE2. Le implementazioni C ++ x86-64 usano sempre SSE2 e i compilatori hanno opzioni come g++ -mfpmath=sseusarlo anche per x86 a 32 bit. Puoi usare gcc -ffloat-storeper forzare l'arrotondamento ovunque anche quando usi x87, oppure puoi impostare la precisione x87 su mantissa a 53 bit: randomascii.wordpress.com/2012/03/21/… .
Peter Cordes,

Ma ancora una buona risposta, per il obsoleto code-gen x87, puoi usare volatileper forzare l'arrotondamento in alcuni punti specifici senza perdere i benefici ovunque.
Peter Cordes,

1
O confondo impreciso con incoerente?
Chipster

49

Da un articolo "Volatile come una promessa" di Dan Saks:

(...) un oggetto volatile è uno il cui valore potrebbe cambiare spontaneamente. Cioè, quando dichiari un oggetto volatile, stai dicendo al compilatore che l'oggetto potrebbe cambiare stato anche se nessuna istruzione nel programma sembra cambiarlo. "

Ecco i collegamenti a tre dei suoi articoli riguardanti la volatileparola chiave:


23

È NECESSARIO utilizzare volatile durante l'implementazione di strutture di dati senza blocco. Altrimenti il ​​compilatore è libero di ottimizzare l'accesso alla variabile, che cambierà la semantica.

Per dirla in altro modo, volatile dice al compilatore che gli accessi a questa variabile devono corrispondere a un'operazione di lettura / scrittura della memoria fisica.

Ad esempio, ecco come viene dichiarato InterlockedIncrement nell'API Win32:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

Non è assolutamente necessario dichiarare una variabile volatile per poter utilizzare InterlockedIncrement.
curiousguy,

Questa risposta è obsoleta ora che C ++ 11 fornisce in std::atomic<LONG>modo da poter scrivere codice senza lucchetto in modo più sicuro senza problemi con carichi puri / negozi puri ottimizzati, riordinati o quant'altro.
Peter Cordes,

10

Una grande applicazione su cui lavoravo nei primi anni '90 conteneva la gestione delle eccezioni basata su C usando setjmp e longjmp. La parola chiave volatile era necessaria sulle variabili i cui valori dovevano essere conservati nel blocco di codice che fungeva da clausola "catch", per evitare che quei vars fossero memorizzati nei registri e cancellati dal longjmp.


10

Nella norma C, uno dei luoghi da utilizzare volatileè con un gestore di segnale. In effetti, nello standard C, tutto ciò che si può fare in sicurezza in un gestore di segnale è modificare una volatile sig_atomic_tvariabile o uscire rapidamente. Infatti, AFAIK, è l'unico posto nella norma C che l'uso di volatileè necessario per evitare comportamenti indefiniti.

ISO / IEC 9899: 2011 §7.14.1.1 La signalfunzione

¶5 Se il segnale si verifica diverso da come risultato della chiamata alla funzione aborto raise, il comportamento non è definito se il gestore del segnale si riferisce a qualsiasi oggetto con durata di memorizzazione statica o del thread che non sia un oggetto atomico senza blocco diverso dall'assegnazione di un valore a un oggetto dichiarato come volatile sig_atomic_t, o il gestore del segnale chiama qualsiasi funzione nella libreria standard diversa dalla abortfunzione, la _Exitfunzione, la quick_exitfunzione o la signalfunzione con il primo argomento uguale al numero del segnale corrispondente al segnale che ha causato l'invocazione del handler. Inoltre, se una tale chiamata alla signalfunzione risulta in un ritorno SIG_ERR, il valore di errnoè indeterminato. 252)

252) Se un segnale viene generato da un gestore di segnale asincrono, il comportamento non è definito.

Ciò significa che nello standard C puoi scrivere:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

e non molto altro.

POSIX è molto più indulgente su ciò che è possibile fare in un gestore di segnale, ma ci sono ancora delle limitazioni (e una delle limitazioni è che la libreria I / O standard - printf()et al - non può essere utilizzata in modo sicuro).


7

Sviluppando per un incorporato, ho un ciclo che verifica una variabile che può essere modificata in un gestore di interrupt. Senza "volatile", il ciclo diventa un noop - per quanto il compilatore può dire, la variabile non cambia mai, quindi ottimizza il controllo.

La stessa cosa si applicherebbe a una variabile che può essere cambiata in un thread diverso in un ambiente più tradizionale, ma lì spesso facciamo chiamate di sincronizzazione, quindi il compilatore non è così libero con l'ottimizzazione.


7

L'ho usato nelle build di debug quando il compilatore insiste sull'ottimizzazione di una variabile che voglio vedere mentre passo il codice.


7

Oltre a usarlo come previsto, volatile viene utilizzato nella metaprogrammazione (modello). Può essere usato per prevenire sovraccarichi accidentali, poiché l'attributo volatile (come const) prende parte alla risoluzione del sovraccarico.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

Questo è legale; entrambi i sovraccarichi sono potenzialmente richiamabili e fanno quasi lo stesso. Il cast nel volatilesovraccarico è legale poiché sappiamo che la barra non passerà Tcomunque non volatile . Tuttavia, la volatileversione è decisamente peggiore, quindi non è mai stata scelta nella risoluzione di sovraccarico se fè disponibile la non volatile .

Si noti che il codice non dipende mai volatiledall'accesso alla memoria.


Potresti per favore approfondire questo con un esempio? Mi aiuterebbe davvero a capire meglio. Grazie!
Batbrat,

" Il cast nel sovraccarico volatile " Un cast è una conversione esplicita. È un costrutto SYNTAX. Molte persone creano questa confusione (anche autori standard).
curiousguy,

6
  1. è necessario utilizzarlo per implementare spinlock così come alcune strutture dati (tutte?) prive di blocco
  2. usalo con operazioni / istruzioni atomiche
  3. mi ha aiutato una volta a superare il bug del compilatore (codice generato erroneamente durante l'ottimizzazione)

5
È meglio usare una libreria, intrinseci del compilatore o codice assembly inline. Volatile non è affidabile.
Zan Lynx,

1
1 e 2 fanno entrambi uso di operazioni atomiche, ma volatile non fornisce semantica atomica e le implementazioni specifiche della piattaforma di atomic supereranno la necessità di usare volatile, quindi per 1 e 2, non sono d'accordo, NON è necessario volatile per questi.

Chi dice qualcosa sulla volatile che fornisce semantica atomica? Ho detto che devi USARE volatile CON le operazioni atomiche e se non pensi che sia vero, guarda le dichiarazioni delle operazioni interbloccate dell'API win32 (questo ragazzo ha anche spiegato questo nella sua risposta)
Mladen Janković,

4

La volatileparola chiave ha lo scopo di impedire al compilatore di applicare eventuali ottimizzazioni sugli oggetti che possono cambiare in modi che non possono essere determinati dal compilatore.

Gli oggetti dichiarati come volatileomessi dall'ottimizzazione perché i loro valori possono essere modificati dal codice al di fuori dell'ambito del codice corrente in qualsiasi momento. Il sistema legge sempre il valore corrente di un volatileoggetto dalla posizione di memoria piuttosto che mantenerne il valore nel registro temporaneo nel punto in cui è richiesto, anche se un'istruzione precedente ha richiesto un valore dallo stesso oggetto.

Considera i seguenti casi

1) Variabili globali modificate da una routine di servizio di interruzione al di fuori dell'ambito.

2) Variabili globali all'interno di un'applicazione multi-thread.

Se non utilizziamo il qualificatore volatile, potrebbero sorgere i seguenti problemi

1) Il codice potrebbe non funzionare come previsto quando l'ottimizzazione è attivata.

2) Il codice potrebbe non funzionare come previsto quando gli interrupt sono abilitati e utilizzati.

Volatile: il migliore amico di un programmatore

https://en.wikipedia.org/wiki/Volatile_(computer_programming)


Il link che hai pubblicato è estremamente obsoleto e non riflette le migliori pratiche attuali.
Tim Seguine,

2

Oltre al fatto che la parola chiave volatile viene utilizzata per dire al compilatore di non ottimizzare l'accesso a qualche variabile (che può essere modificata da un thread o da una routine di interruzione), può anche essere usata per rimuovere alcuni bug del compilatore - SÌ, può essere ---.

Ad esempio, ho lavorato su una piattaforma integrata dove il compilatore stava facendo alcune assunzioni errate riguardo al valore di una variabile. Se il codice non fosse ottimizzato, il programma funzionerebbe correttamente. Con le ottimizzazioni (che erano davvero necessarie perché era una routine critica) il codice non funzionava correttamente. L'unica soluzione (sebbene non molto corretta) era dichiarare la variabile 'difettosa' come volatile.


3
È un presupposto errato l'idea che il compilatore non ottimizzi l'accesso ai volatili. Lo standard non sa nulla di ottimizzazioni. Il compilatore è tenuto a rispettare ciò che impone lo standard, ma è libero di fare qualsiasi ottimizzazione che non interferisca con il comportamento normale.
Terminus,

3
Dalla mia esperienza il 99,9% di tutti i "bug" di ottimizzazione nel braccio gcc sono errori da parte del programmatore. Non ho idea se questo si applica a questa risposta. Solo un rant sull'argomento generale
odinthenerd

@Terminus " È un presupposto errato l'idea che il compilatore non ottimizzi l'accesso ai volatili " Fonte?
curiousguy,

2

Il tuo programma sembra funzionare anche senza volatileparole chiave? Forse questo è il motivo:

Come accennato in precedenza, la volatileparola chiave aiuta per casi come

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

Ma sembra non esserci quasi alcun effetto una volta chiamata una funzione esterna o non in linea. Per esempio:

while( *p!=0 ) { g(); }

Quindi con o senza volatilequasi lo stesso risultato viene generato.

Fintanto che g () può essere completamente integrato, il compilatore può vedere tutto ciò che sta succedendo e può quindi ottimizzare. Ma quando il programma effettua una chiamata in un punto in cui il compilatore non può vedere cosa sta succedendo, non è più sicuro per il compilatore fare ipotesi più. Quindi il compilatore genererà il codice che legge sempre direttamente dalla memoria.

Ma attenzione al giorno, quando la tua funzione g () diventa in linea (o a causa di cambiamenti espliciti o a causa dell'intelligenza del compilatore / linker), il tuo codice potrebbe rompersi se dimentichi la volatileparola chiave!

Pertanto consiglio di aggiungere la volatileparola chiave anche se il programma sembra funzionare senza. Rende l'intenzione più chiara e più solida rispetto ai cambiamenti futuri.


Si noti che una funzione può avere il suo codice integrato mentre continua a generare un riferimento (risolto al momento del collegamento) alla funzione struttura; questo sarà il caso di una funzione ricorsiva parzialmente incorporata. Una funzione potrebbe anche essere "incorporata" semantica dal compilatore, ovvero il compilatore presuppone che gli effetti collaterali e il risultato siano all'interno dei possibili effetti collaterali e dei risultati possibili in base al suo codice sorgente, pur continuando a non incorporarlo. Questo si basa sull'efficace "One Definition Rule" che stabilisce che tutte le definizioni di un'entità devono essere effettivamente equivalenti (se non esattamente identiche).
curioso

Evitare portabilmente l'inserimento di una chiamata (o "inline" della sua semantica) da una funzione il cui corpo è visibile dal compilatore (anche al momento del collegamento con l'ottimizzazione globale) è possibile utilizzando un volatilepuntatore a funzione qualificata:void (* volatile fun_ptr)() = fun; fun_ptr();
curiousguy

2

All'inizio di C, i compilatori avrebbero interpretato tutte le azioni che leggevano e scrivevano valori come operazioni di memoria, da eseguire nella stessa sequenza delle letture e delle scritture visualizzate nel codice. L'efficienza potrebbe essere notevolmente migliorata in molti casi se ai compilatori fosse data una certa libertà di riordinare e consolidare le operazioni, ma c'era un problema con questo. Anche le operazioni venivano spesso specificate in un certo ordine semplicemente perché era necessario specificarle in un certo ordine, e quindi il programmatore sceglieva una delle tante alternative altrettanto valide, che non era sempre il caso. A volte sarebbe importante che determinate operazioni avvengano in una sequenza particolare.

Esattamente quali dettagli del sequenziamento sono importanti varieranno a seconda della piattaforma di destinazione e del campo di applicazione. Invece di fornire un controllo particolarmente dettagliato, lo Standard ha optato per un modello semplice: se una sequenza di accessi viene eseguita con valori non qualificati volatile, un compilatore può riordinarli e consolidarli come meglio ritiene opportuno. Se un'azione viene eseguita con un volatilevalore qualificato, un'implementazione di qualità dovrebbe offrire qualsiasi ulteriore garanzia di ordinamento potrebbe essere richiesta dal codice indirizzato alla piattaforma e al campo di applicazione previsti, senza la necessità di ricorrere a una sintassi non standard.

Sfortunatamente, anziché identificare le garanzie di cui i programmatori avrebbero bisogno, molti compilatori hanno optato invece per offrire le garanzie minime richieste dalla norma. Questo rende volatilemolto meno utile di quanto dovrebbe essere. Su gcc o clang, per esempio, un programmatore che ha bisogno di implementare un "mutex" di base [uno in cui un'attività che ha acquisito e rilasciato un mutex non lo farà di nuovo finché l'altra attività non lo ha fatto] deve fare uno di quattro cose:

  1. Metti l'acquisizione e il rilascio del mutex in una funzione che il compilatore non può incorporare e alla quale non può applicare l'ottimizzazione dell'intero programma.

  2. Qualifica tutti gli oggetti custoditi dal mutex come volatile--qualcosa che non dovrebbe essere necessario se tutti gli accessi si verificano dopo aver acquisito il mutex e prima di rilasciarlo.

  3. Utilizzare livello di ottimizzazione 0 per forzare il compilatore a generare codice come se tutti gli oggetti che non sono qualificati registersono volatile.

  4. Usa le direttive specifiche di gcc.

Al contrario, quando si utilizza un compilatore di qualità superiore che è più adatto per la programmazione di sistemi, come icc, si avrebbe un'altra opzione:

  1. Assicurati che una volatilescrittura qualificata venga eseguita ovunque sia necessario acquisire o rilasciare.

L'acquisizione di un "mutex manuale" di base richiede una volatilelettura (per vedere se è pronta) e non dovrebbe richiedere anche una volatilescrittura (l'altra parte non proverà a riacquistarla finché non viene restituita) ma deve eseguire una volatilescrittura senza significato è ancora meglio di una qualsiasi delle opzioni disponibili in gcc o clang.


1

Un uso che dovrei ricordare è che, nella funzione del gestore del segnale, se si desidera accedere / modificare una variabile globale (ad esempio, contrassegnarla come exit = true) è necessario dichiarare tale variabile come 'volatile'.


1

Tutte le risposte sono eccellenti Ma soprattutto, vorrei condividere un esempio.

Di seguito è riportato un piccolo programma cpp:

#include <iostream>

int x;

int main(){
    char buf[50];
    x = 8;

    if(x == 8)
        printf("x is 8\n");
    else
        sprintf(buf, "x is not 8\n");

    x=1000;
    while(x > 5)
        x--;
    return 0;
}

Ora, generiamo l'assembly del codice sopra (e incollerò solo quelle parti dell'assembly rilevanti qui):

Il comando per generare l'assembly:

g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp

E l'assemblea:

main:
.LFB1594:
    subq    $40, %rsp    #,
    .seh_stackalloc 40
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC0(%rip), %rcx     #,
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:10:         printf("x is 8\n");
    call    _ZL6printfPKcz.constprop.0   #
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    movl    $5, x(%rip)  #, x
    addq    $40, %rsp    #,
    ret 
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

Nell'assembly è possibile vedere che il codice assembly non è stato generato sprintfperché il compilatore ha ipotizzato che xnon cambierà al di fuori del programma. E lo stesso vale per il whileloop. whileil ciclo è stato completamente rimosso a causa dell'ottimizzazione perché il compilatore lo vedeva come un codice inutile e quindi direttamente assegnato 5a x(vedi movl $5, x(%rip)).

Il problema si verifica quando cosa succede se un processo / hardware esterno cambierebbe il valore di un punto xtra x = 8;e if(x == 8). Ci aspetteremmo che il elseblocco funzioni, ma sfortunatamente il compilatore ha eliminato quella parte.

Ora, al fine di risolvere questo problema, nel assembly.cpp, cambiamo int x;per volatile int x;e rapidamente vedere il codice assembly generato:

main:
.LFB1594:
    subq    $104, %rsp   #,
    .seh_stackalloc 104
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:9:     if(x == 8)
    movl    x(%rip), %eax    # x, x.1_1
 # assembly.cpp:9:     if(x == 8)
    cmpl    $8, %eax     #, x.1_1
    je  .L11     #,
 # assembly.cpp:12:         sprintf(buf, "x is not 8\n");
    leaq    32(%rsp), %rcx   #, tmp93
    leaq    .LC0(%rip), %rdx     #,
    call    _ZL7sprintfPcPKcz.constprop.0    #
.L7:
 # assembly.cpp:14:     x=1000;
    movl    $1000, x(%rip)   #, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_15
    cmpl    $5, %eax     #, x.3_15
    jle .L8  #,
    .p2align 4,,10
.L9:
 # assembly.cpp:16:         x--;
    movl    x(%rip), %eax    # x, x.4_3
    subl    $1, %eax     #, _4
    movl    %eax, x(%rip)    # _4, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_2
    cmpl    $5, %eax     #, x.3_2
    jg  .L9  #,
.L8:
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    addq    $104, %rsp   #,
    ret 
.L11:
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC1(%rip), %rcx     #,
    call    _ZL6printfPKcz.constprop.1   #
    jmp .L7  #
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

Qui potete vedere che i codici di assemblaggio per sprintf, printfe whiledel ciclo sono stati generati. Il vantaggio è che se la xvariabile viene modificata da un programma o hardware esterno, sprintfverrà eseguita parte del codice. Allo stesso modo il whileloop può essere utilizzato per l'attesa occupata ora.


0

Altre risposte citano già di evitare alcune ottimizzazioni al fine di:

  • usa i registri associati alla memoria (o "MMIO")
  • scrivere driver di dispositivo
  • consentire un debug più semplice dei programmi
  • rendere i calcoli in virgola mobile più deterministici

La volatilità è essenziale ogni volta che è necessario che un valore appaia proveniente dall'esterno ed essere imprevedibile ed evitare le ottimizzazioni del compilatore basate su un valore noto e quando un risultato non è effettivamente utilizzato ma è necessario che sia calcolato, oppure viene utilizzato ma vuoi calcolarlo più volte per un benchmark e hai bisogno che i calcoli inizino e finiscano in punti precisi.

Una lettura volatile è come un'operazione di input (like scanfo uso di cin): il valore sembra provenire dall'esterno del programma, quindi qualsiasi calcolo che ha una dipendenza dal valore deve iniziare dopo di esso .

Una scrittura volatile è come un'operazione di output (like printfo uso di cout): il valore sembra essere comunicato al di fuori del programma, quindi se il valore dipende da un calcolo, deve essere terminato prima .

Quindi una coppia di lettura / scrittura volatile può essere utilizzata per domare i benchmark e rendere significativa la misurazione del tempo .

Senza volatile, il compilatore potrebbe essere avviato prima dal compilatore, poiché nulla impedirebbe il riordino dei calcoli con funzioni come la misurazione del tempo .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.