É permitido ao compilador dobrar constantemente um volátil local?


25

Considere este código simples:

void g();

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

https://godbolt.org/z/I2kBY7

Você pode ver que gccnem clangotimiza a chamada potencial para g. Isso está correto no meu entendimento: A máquina abstrata deve assumir que as volatilevariáveis ​​podem mudar a qualquer momento (devido a serem, por exemplo, mapeadas por hardware), portanto, dobrar constantemente a falseinicialização na ifverificação estaria errado.

Mas o MSVC elimina gtotalmente a chamada (mantendo as leituras e gravações no volatileentanto!). Esse comportamento é compatível com os padrões?


Antecedentes: Ocasionalmente uso esse tipo de construção para ativar / desativar a saída de depuração on-the-fly: O compilador sempre deve ler o valor da memória, portanto, alterar a variável / memória durante a depuração deve modificar o fluxo de controle adequadamente . A saída MSVC relê o valor, mas o ignora (provavelmente devido à constante dobragem e / ou eliminação do código morto), o que obviamente derrota minhas intenções aqui.


Editar% s:

  • A eliminação das leituras e gravações volatileé discutida aqui: É permitido que um compilador otimize uma variável volátil local? (obrigado Nathan!). Eu acho que o padrão é bastante claro de que essas leituras e gravações devem acontecer. Mas essa discussão não cobre se é legal para o compilador considerar os resultados dessas leituras como garantidos e otimizar com base nisso. Suponho que isso seja sub / não especificado no padrão, mas ficaria feliz se alguém me provasse errado.

  • É claro que posso criar xuma variável não local para contornar o problema. Esta questão é mais por curiosidade.


3
Isso parece um bug óbvio do compilador para mim.
— Sam Varshavchik

11
Tanto quanto eu sei, isso é legal sob a regra como se. O compilador pode provar que, embora o objeto seja volátil, não há como o estado ser modificado para que possa ser desdobrado. Não estou confiante o suficiente para responder isso, mas acho que está correto.
— NathanOliver 16/10/19

3
Mas acho que o argumento do OP de que a variável pode ser modificada por um depurador é razoável. Talvez alguém deva registrar um relatório de erro na MSVC.
— Brian

2
@curiousguy Mesmo se você descartar o resultado e / ou assumir o valor exato, ainda o leu.
— Deduplicator

2
Curiosamente, isso só é possível para x64. A versão x86 ainda chama g () godbolt.org/z/nc3Y-f
— Jerry Jeremiah

Respostas:


2

Eu acho que [intro.execution] (número do parágrafo varia) poderia ser usado para explicar o comportamento da MSVC:

Uma instância de cada objeto com duração de armazenamento automático é associada a cada entrada em seu bloco. Esse objeto existe e retém seu último valor armazenado durante a execução do bloco e enquanto o bloco está suspenso ...

O padrão não permite a eliminação de uma leitura através de um valor de volume volátil, mas o parágrafo acima pode ser interpretado como permitindo prever o valor false.


BTW, o Padrão C (N1570 6.2.4 / 2) afirma que

Um objeto existe, tem um endereço constante e mantém seu último valor armazenado ao longo de sua vida útil. 34


34) No caso de um objeto volátil, o último armazenamento não precisa ser explícito no programa.

Não está claro se poderia haver um armazenamento não explícito em um objeto com duração de armazenamento automático na memória C / modelo de objeto.


Concorde que o compilador pode saber quando são possíveis armazenamentos não explícitos na plataforma de destino.
— MM

11
Então, se isso for verdade, os objetos voláteis locais são (pelo menos no MSVC) totalmente inúteis? Existe algo que a adição volatilelhe compre (exceto leituras / gravações supérfluas) se for ignorada para fins de otimização?
— quer

11
@ MaxLanghof Há uma diferença entre totalmente inútil e não ter o efeito que você deseja / espera.
— Deduplicator

11
@MaxLanghof Os resultados intermediários dos cálculos de ponto flutuante às vezes são promovidos para um registro de 80 bits, causando problemas de precisão. Eu acredito que este é um gcc-ism e é evitado declarando todas essas dobras como voláteis. Veja: gcc.gnu.org/bugzilla/show_bug.cgi?id=323
— ForeverLearning

11
@ philipxy PS Veja minha resposta Eu sei que o acesso é definido pela implementação. A questão não é sobre acesso (o objeto é acessado), mas a previsão do valor.
— Language Lawyer

2

TL; DR O compilador pode fazer o que quiser em cada acesso volátil. Mas a documentação deve lhe informar .-- "A semântica de um acesso através de um valor volátil volátil é definida pela implementação".


O padrão define para um programa sequências permitidas de "acessos voláteis" e outros "comportamentos observáveis" (alcançados através de "efeitos colaterais") que uma implementação deve respeitar de acordo com "a regra 'como se'".

Mas o padrão diz (minha ênfase em negrito):

Rascunho de trabalho, padrão para linguagem de programação C ++
Número do documento: N4659
Data: 2017-03-21

§ 10.1.7.1 Os qualificadores cv

5 A semântica de um acesso através de um valor de volume volátil é definida pela implementação. [...]

Da mesma forma para dispositivos interativos (ênfase em negrito):

§ 4.6 Execução do programa

5 Uma implementação em conformidade executando um programa bem formado deve produzir o mesmo comportamento observável que uma das possíveis execuções da instância correspondente da máquina abstrata com o mesmo programa e a mesma entrada. [...]

7 Os requisitos mínimos em uma implementação em conformidade são:

(7.1) - Os acessos através de valores voláteis são avaliados estritamente de acordo com as regras da máquina abstrata.
(7.2) - No término do programa, todos os dados gravados em arquivos devem ser idênticos a um dos resultados possíveis que a execução do programa, de acordo com a semântica abstrata, teria produzido.
(7.3) - A dinâmica de entrada e saída de dispositivos interativos deve ocorrer de maneira que a saída solicitada seja realmente entregue antes que um programa aguarde a entrada. O que constitui um dispositivo interativo é definido pela implementação.

Estes são chamados coletivamente de comportamento observável do programa. [...]

(De qualquer forma, qual código específico é gerado para um programa não é especificado pelo padrão.)

Portanto, embora o padrão diga que acessos voláteis não podem ser eliminados das seqüências abstratas dos efeitos colaterais da máquina abstrata e consequentes comportamentos observáveis ​​que algum código (talvez) define, você não pode esperar que nada seja refletido no código de objeto ou no mundo real comportamento, a menos que a documentação do compilador informe o que constitui um acesso volátil . O mesmo vale para dispositivos interativos.

Se você está interessado em volatilidade em relação às seqüências abstratas dos efeitos colaterais da máquina abstrata e / ou comportamentos observáveis ​​consequentes que algum código (talvez) define, então o diga . Mas se você estiver interessado em qual código de objeto correspondente é gerado , deve interpretá-lo no contexto de seu compilador e compilação .

Cronicamente, as pessoas acreditam erroneamente que, para acessos voláteis, uma avaliação / leitura abstrata da máquina causa uma leitura implementada e uma atribuição / gravação abstrata da máquina causa uma gravação implementada. Não há base para essa crença na documentação de implementação ausente dizendo isso. Quando / se a implementação diz que realmente faz algo com um "acesso volátil", as pessoas são justificadas em esperar que algo - talvez, a geração de determinado código de objeto.


11
Quero dizer, isso se resume a "se eu criar uma máquina em que todos os efeitos colaterais mencionados não funcionem, então eu tenho uma implementação legal em C ++ compilando todos os programas em um não operacional" . É claro que estamos interessados ​​em efeitos praticamente observáveis, pois os efeitos colaterais abstratos da máquina são tautologicamente abstratos. Isso requer algum conceito básico de efeitos colaterais, e eu argumentaria que "acessos voláteis levam a instruções explícitas de acesso à memória" faz parte disso (mesmo que o padrão não se importe), então eu realmente não comprei o "say se você quiser código em vez de semântica abstrata ". Ainda assim, +1.
— precisa

Sim, a qualidade da implementação é relevante. No entanto, além das gravações em arquivos, "observáveis" [sic] são definidos pela implementação. Se você deseja definir pontos de interrupção, acessar determinadas memórias reais, ter 'volátil' ignorado etc. em leituras e gravações voláteis abstratas, é necessário que o escritor do compilador produza o código apropriado . PS No C ++, "efeito colateral" não é usado para o comportamento de observação em si, é usado para descrever a avaliação de ordem parcial de subexpressões.
— philipxy

Gostaria de explicar o [sic]? Qual fonte você está citando e qual é o erro?
— Max Langhof

Resic Apenas quero dizer que uma máquina abstrata observável só é observável no mundo real se e como a implementação diz que é.
— philipxy

11
Você está dizendo que uma implementação poderia alegar que não existe um dispositivo interativo, para que qualquer programa possa fazer qualquer coisa e ainda assim esteja correto? (Nota: Eu não entendo a sua ênfase em dispositivos interativos.)
— curiousguy

-1

Eu acredito que é legal pular o cheque.

O parágrafo que todo mundo gosta de citar

34) No caso de um objeto volátil, o último armazenamento não precisa ser explícito no programa

não implica que uma implementação deva assumir que tais armazenamentos são possíveis a qualquer momento ou para qualquer variável volátil. Uma implementação sabe quais lojas são possíveis. Por exemplo, é inteiramente razoável supor que tais gravações implícitas só acontecem para variáveis ​​voláteis mapeadas para registros de dispositivos e que esse mapeamento só é possível para variáveis ​​com ligação externa. Ou uma implementação pode assumir que tais gravações só ocorrem em locais de memória alinhados e com tamanho de palavra.

Dito isto, acho que o comportamento do MSVC é um bug. Não há motivo do mundo real para otimizar a chamada. Essa otimização pode ser compatível, mas é desnecessariamente má.


Você pode explicar por que é mau? No código mostra, a função literalmente nunca pode ser chamada.
— David Schwartz

@DavidSchwartz Você só pode concluir que depois de especificar a semântica das variáveis ​​voláteis locais (consulte o parágrafo citado acima). O próprio padrão observa que volatileé suposto ser uma dica para a implementação que o valor pode mudar por meios desconhecidos para a implementação.
— Max Langhof

@ MaxLanghof Não há como a implementação lidar corretamente com algo desconhecido. O que as plataformas úteis realmente fazem é especificar o que você pode e não pode usar volatilenessa plataforma e fora dessa especificação, sempre será uma porcaria.
— David Schwartz

@DavidSchwartz É claro que pode - seguindo a semântica (em particular, as leituras e gravações) da máquina abstrata. Talvez não seja possível otimizar corretamente - esse é o objetivo do padrão. Agora, é uma nota e, portanto, não é normativa, e, como dissemos, a implementação pode especificar o que volatilefaz e manter isso. O que quero dizer é que o próprio código (de acordo com a máquina padrão / abstrata C ++) não permite que você determine se gpode ser chamado.
— precisa

@DavidSchwartz O código não mostra nada parecido, porque a ausência de gravações implícitas não segue o código.
— n. 'pronomes' m.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.