it-swarm-eu.dev

Perché esiste la volatilità?

Cosa fa la parola chiave volatile? In C++ che problema risolve?

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

201
theschmitzer

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 ragazzo aveva 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, si sbarazza di quel codice!) E il mio codice procederebbe senza aver acquisito il semaforo, causando problemi in seguito.

247
Doug T.

volatile è necessario quando si sviluppano sistemi embedded o driver di dispositivo, dove è 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 parola chiave volatile per assicurarsi che tali accessi non siano ottimizzati dal compilatore.

78
ChrisN

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 è fantastico, 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.

68
tfinniga

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 parola chiave volatile:

45
MikeZ

È 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
);
23

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
Jeff Doar

Nello standard C, uno dei posti in cui usare 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 variabile volatile sig_atomic_t O uscire rapidamente. Infatti, AFAIK, è l'unico posto nella norma C che è richiesto l'uso di volatile per evitare comportamenti indefiniti.

ISO/IEC 9899: 2011 §7.14.1.1 La funzione signal

¶5 Se il segnale si verifica diverso da come risultato della chiamata alla funzione abort o raise, il comportamento non è definito se il gestore del segnale si riferisce a qualsiasi oggetto con durata di memorizzazione statica o thread che non è un oggetto atomico senza blocco diverso dall'assegnazione di un valore a un oggetto dichiarato come volatile sig_atomic_t, oppure il gestore del segnale chiama qualsiasi funzione nella libreria standard diversa dalla funzione abort, la _Exit, La funzione quick_exit O la funzione signal con il primo argomento uguale al numero del segnale corrispondente al segnale che ha causato l'invocazione del gestore. Inoltre, se una tale chiamata alla funzione signal 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 ).

10
Jonathan Leffler

Oltre ad 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 sovraccarico volatile è legale poiché sappiamo che la barra non passerà comunque un T non volatile. La versione volatile è comunque peggiore, quindi non è mai stata scelta nella risoluzione di sovraccarico se è disponibile la non volatile f.

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

7
MSalters

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

7
indentation

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
Arkadiy
  1. è necessario utilizzarlo per implementare spinlock e 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)
6
Mladen Janković

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

Gli oggetti dichiarati come volatile vengono omessi 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 oggetto volatile 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)

4
roottraveller

Il tuo programma sembra funzionare anche senza la parola chiave volatile? Forse questo è il motivo:

Come accennato in precedenza, la parola chiave volatile aiuta in 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 volatile viene generato quasi lo stesso risultato.

Finché g() può essere completamente integrato, il compilatore può vedere tutto quello che sta succedendo e può quindi ottimizzare. Ma quando il programma fa una chiamata a un posto dove il compilatore non può vedere cosa sta succedendo, non è più sicuro per il compilatore fare più assunzioni, quindi il compilatore genererà il codice che legge sempre direttamente dalla memoria.

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

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

2
Joachim

All'inizio di C, i compilatori avrebbero interpretato tutte le azioni che leggevano e scrivevano i 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 sono state spesso specificate in un certo ordine semplicemente perché era necessario specificarle nell'ordine alcuni, e quindi il programmatore ha scelto una delle tante alternative altrettanto valide, non è sempre stato così. 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 valore qualificato volatile, 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 volatile molto meno utile di quanto dovrebbe essere. Su gcc o clang, ad 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 fino a quando l'altro 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 il livello di ottimizzazione 0 per forzare il compilatore a generare codice come se tutti gli oggetti non qualificati register siano volatile.

  4. Usa le direttive specifiche di gcc.

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

  1. Assicurarsi che una scrittura qualificata volatile venga eseguita ovunque sia necessario acquisire o rilasciare.

L'acquisizione di un "mutex" di base richiede una lettura volatile (per vedere se è pronta) e non dovrebbe richiedere anche una scrittura volatile (l'altra parte non proverà a riacquistarlo fino a quando non viene restituito), ma dover eseguire una scrittura volatile insignificante è ancora migliore di una qualsiasi delle opzioni disponibili in gcc o clang.

2
supercat

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 sata per rimuovere alcuni bug del compilatore - SÌ può essere ---.

Ad esempio, ho lavorato su una piattaforma integrata in cui 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.

2
INS

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
bugs king