Il compilatore può piegare costantemente una volatile locale?


25

Considera questo semplice codice:

void g();

void foo()
{
    volatile bool x = false;
    if (x)
        g();
}

https://godbolt.org/z/I2kBY7

Puoi vedere che gccclangottimizzare né ottimizzare la potenziale chiamata a g. Questo è corretto nella mia comprensione: la macchina astratta è quella di supporre che le volatilevariabili possano cambiare in qualsiasi momento (a causa di essere mappate dall'hardware), quindi piegare costantemente l' falseinizializzazione nel ifcontrollo sarebbe sbagliato.

Ma MSVC elimina completamente la chiamata g(mantenendo le letture e le scritture al volatilepensiero!). Questo comportamento è conforme allo standard?


Background: Occasionalmente utilizzo questo tipo di costrutto per essere in grado di attivare / disattivare l'output di debug al volo: il compilatore deve sempre leggere il valore dalla memoria, quindi cambiando quella variabile / memoria durante il debug si dovrebbe modificare il flusso di controllo di conseguenza . L'output di MSVC rilegge il valore ma lo ignora (presumibilmente a causa della costante piegatura e / o eliminazione del codice morto), che ovviamente sconfigge qui le mie intenzioni.


modifiche:

  • L'eliminazione delle letture e delle scritture volatileviene discussa qui: è consentito a un compilatore di ottimizzare una variabile volatile locale? (grazie Nathan!). Penso che lo standard sia abbondantemente chiaro che quelle letture e scritture debbano avvenire. Ma questa discussione non copre se sia legale per il compilatore dare per scontati i risultati di tali letture e ottimizzarli sulla base di ciò. Suppongo che questo sia sotto / non specificato nello standard, ma sarei felice se qualcuno mi dimostrasse che mi sbagliavo.

  • Ovviamente posso creare xuna variabile non locale per risolvere il problema. Questa domanda è più per curiosità.


3
Mi sembra un ovvio bug del compilatore.
Sam Varshavchik,

1
Per quanto ne so, questo è legale secondo la regola del "se". Il compilatore può dimostrare che anche se l'oggetto è volatile, non è possibile modificarne lo stato in modo che possa essere ripiegato. Non sono abbastanza sicuro di poterlo inserire in una risposta, ma ritengo che sia corretto.
NathanOliver,

3
Ma penso che l'argomento del PO secondo cui la variabile possa essere modificata da un debugger sia ragionevole. Forse qualcuno dovrebbe presentare una segnalazione di bug con MSVC.
Brian,

2
@curiousguy Anche se scarti il ​​risultato e / o assumi il valore esatto, lo hai ancora letto.
Deduplicatore,

2
È interessante notare che lo fa solo per x64. La versione x86 chiama ancora g () godbolt.org/z/nc3Y-f
Jerry Jeremiah,

Risposte:


2

Penso che [intro.execution] (il numero di paragrafo vari) potrebbe essere usato per spiegare il comportamento di MSVC:

Un'istanza di ciascun oggetto con durata di memorizzazione automatica è associata a ciascuna voce nel suo blocco. Tale oggetto esiste e mantiene il suo ultimo valore memorizzato durante l'esecuzione del blocco e mentre il blocco è sospeso ...

Lo standard non consente l'eliminazione di una lettura attraverso un valore volatile, ma il paragrafo sopra potrebbe essere interpretato nel senso che consente di prevedere il valore false.


A proposito, lo standard C (N1570 6.2.4 / 2) dice che

Un oggetto esiste, ha un indirizzo costante e conserva il suo ultimo valore memorizzato per tutta la sua durata. 34


34) Nel caso di un oggetto volatile, l'ultimo archivio non deve essere esplicito nel programma.

Non è chiaro se ci potrebbe essere un archivio non esplicito in un oggetto con durata di memorizzazione automatica nella memoria C / modello a oggetti.


Accetto che il compilatore possa sapere quando sono possibili negozi non espliciti sulla piattaforma di destinazione
MM

1
Quindi, se questo è vero, allora gli oggetti volatili locali sono (almeno su MSVC) del tutto inutili? C'è qualcosa che ti aggiunge volatile(oltre alle letture / scritture superflue) se ignorato a fini di ottimizzazione?
Max Langhof,

1
@MaxLanghof C'è una differenza tra completamente inutile e non avere l'effetto che desideri / ti aspetti.
Deduplicatore,

1
@MaxLanghof I risultati intermedi dei calcoli in virgola mobile a volte vengono promossi in un registro a 80 bit causando problemi di precisione. Credo che questo sia un gcc-ismo ed è evitato dichiarando tutti i doppi come volatili. Vedi: gcc.gnu.org/bugzilla/show_bug.cgi?id=323
ForeverLearning

1
@philipxy PS Vedi la mia risposta So che l'accesso è definito dall'implementazione. La domanda non riguarda l'accesso (si accede all'oggetto), ma la previsione del valore.
Avvocato linguistico,

2

TL; DR Il compilatore può fare quello che vuole su ogni accesso volatile. Ma la documentazione deve dirti: "La semantica di un accesso attraverso un valore volatile è definita dall'implementazione".


Lo standard definisce per un programma le sequenze consentite di "accessi volatili" e altri "comportamenti osservabili" (raggiunti tramite "effetti collaterali") che un'implementazione deve rispettare secondo la "regola dell'as-se".

Ma lo standard dice (la mia enfasi sul grassetto):

Bozza di lavoro, standard per il linguaggio di programmazione C ++
Numero documento: N4659
Data: 21-03-2017

§ 10.1.7.1 I qualificatori cv

5 La semantica di un accesso attraverso un valore volatile è definita dall'implementazione. [...]

Allo stesso modo per i dispositivi interattivi (la mia enfasi sul grassetto):

§ 4.6 Esecuzione del programma

5 Un'implementazione conforme che esegue un programma ben formato deve produrre lo stesso comportamento osservabile di una delle possibili esecuzioni dell'istanza corrispondente della macchina astratta con lo stesso programma e lo stesso input. [...]

7 I requisiti minimi per un'implementazione conforme sono:

(7.1) - Gli accessi attraverso i valori volatili sono valutati rigorosamente secondo le regole della macchina astratta.
(7.2) - Al termine del programma, tutti i dati scritti in file devono essere identici a uno dei possibili risultati che avrebbe prodotto l'esecuzione del programma in base alla semantica astratta.
(7.3) - Le dinamiche di input e output dei dispositivi interattivi devono avvenire in modo tale che il prompt di output sia effettivamente consegnato prima che un programma attenda l'input. Ciò che costituisce un dispositivo interattivo è definito dall'implementazione.

Questi sono collettivamente definiti comportamento osservabile del programma. [...]

(Comunque quale codice specifico viene generato per un programma non è specificato dallo standard.)

Quindi, sebbene lo standard affermi che gli accessi volatili non possono essere elusi dalle sequenze astratte di effetti collaterali astratti della macchina e conseguenti comportamenti osservabili che alcuni codici (forse) definiscono, non ci si può aspettare che qualcosa si rifletta nel codice oggetto o nel mondo reale comportamento a meno che la documentazione del compilatore non indichi cosa costituisce un accesso instabile . Idem per dispositivi interattivi.

Se sei interessato alla volatilità di fronte alle sequenze astratte di effetti collaterali astratti della macchina e / o conseguenti comportamenti osservabili che alcuni codici (forse) definiscono, allora dillo . Ma se sei interessato a quale codice oggetto corrispondente viene generato , devi interpretarlo nel contesto del tuo compilatore e compilazione .

Cronicamente le persone credono erroneamente che per accessi volatili una valutazione / lettura di una macchina astratta provochi una lettura implementata e un'assegnazione / scrittura di una macchina astratta causi una scrittura implementata. Non vi è alcuna base per questa convinzione in assenza di documentazione sull'attuazione che lo affermi. Quando / iff l'implementazione dice che in realtà fa qualcosa su un "accesso volatile", le persone sono giustificate nell'aspettarsi che qualcosa - forse, la generazione di un certo codice oggetto.


1
Voglio dire, questo si riduce a "se trovo una macchina in cui tutti gli effetti collaterali citati sono no-ops allora ho un'implementazione legale in C ++ compilando ogni programma in una no-op" . Naturalmente siamo interessati a effetti praticamente osservabili, dal momento che gli effetti collaterali della macchina astratta sono tautologicamente astratti. Ciò richiede alcuni concetti base di effetti collaterali e riterrei che "gli accessi volatili portano a istruzioni esplicite di accesso alla memoria" fa parte di questo (anche se lo standard non se ne frega), quindi non compro davvero il "dire se vuoi il codice invece della semantica astratta ". Ancora, +1.
Max Langhof,

Sì, la qualità dell'attuazione è pertinente. Tuttavia, oltre a scrivere su file, "osservabili" [sic] sono definiti dall'implementazione. Se si desidera essere in grado di impostare i punti di interruzione, accedere a determinate memorie effettive, ignorare 'volatile', ecc. Su letture e scritture volatili astratte, è necessario far sì che il proprio compilatore emetta un codice appropriato . PS In C ++ l '"effetto collaterale" non è usato per il comportamento di Observabke in sé, ma è usato per descrivere la valutazione parziale dell'ordine delle sottoespressioni.
Philipxy,

Vuoi spiegare il [sic]? Quale fonte stai citando e qual è l'errore?
Max Langhof,

In realtà intendo solo che una macchina astratta osservabile è osservabile solo nel mondo reale se e come lo dice l'implementazione.
philipxy,

1
Stai dicendo che un'implementazione potrebbe affermare che non esiste un dispositivo interattivo, quindi qualsiasi programma può fare qualsiasi cosa e sarebbe comunque corretto? (Nota: non capisco la tua enfasi sui dispositivi interattivi.)
curiousguy

-1

Credo che sia legale saltare l'assegno.

Il paragrafo che a tutti piace citare

34) Nel caso di un oggetto volatile, l'ultimo archivio non deve essere esplicito nel programma

non implica che un'implementazione debba presumere che tali negozi siano possibili in qualsiasi momento o per qualsiasi variabile volatile. Un'implementazione sa quali negozi sono possibili. Ad esempio, è del tutto ragionevole supporre che tali scritture implicite avvengano solo per variabili volatili che sono mappate ai registri dei dispositivi e che tale mappatura è possibile solo per variabili con collegamento esterno. Oppure un'implementazione può presumere che tali scritture si aprano solo a posizioni di memoria allineate a parole, allineate a parole.

Detto questo, penso che il comportamento di MSVC sia un bug. Non esiste un motivo reale per ottimizzare la chiamata. Tale ottimizzazione può essere conforme, ma è inutilmente malvagia.


Puoi spiegare perché è malvagio? Nel codice mostra, la funzione non può letteralmente mai essere chiamata.
David Schwartz,

@DavidSchwartz Puoi concludere che dopo aver specificato la semantica delle variabili volatili locali (vedi il paragrafo citato sopra). Lo standard stesso rileva che volatilesi suppone che sia un suggerimento per l'implementazione che il valore può cambiare con mezzi sconosciuti all'implementazione.
Max Langhof,

@MaxLanghof In nessun modo l'implementazione può gestire correttamente qualcosa di sconosciuto. Quello che effettivamente fanno le piattaforme utili è specificare ciò che puoi e non puoi usare volatilesu quella piattaforma e al di fuori di quella specifica, sarà sempre uno schifo.
David Schwartz,

@DavidSchwartz Certo che può - seguendo la semantica (in particolare, le letture e le scritture) della macchina astratta. Potrebbe non essere in grado di ottimizzare correttamente - questo è il punto dello standard. Ora, è una nota e quindi non normativa, e come entrambi abbiamo detto, l'implementazione può specificare cosa volatilefa e attenersi a quello. Il mio punto è che il codice stesso (secondo la macchina standard / astratta C ++) non consente di determinare se gpuò essere chiamato.
Max Langhof,

@DavidSchwartz Il codice non mostra nulla del genere, poiché l'assenza di scritture implicite non segue il codice.
n. 'pronomi' m.
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.