Existe uma técnica intrigante que faz o seu compilador detectar possíveis condições de corrida que depende muito da palavra-chave volátil. Você pode ler sobre isso em http://www.ddj.com/cpp/184403766 .
Neno Ganchev 16/09/08
Este é um bom recurso com um exemplo de quando volatilepode ser usado de maneira eficaz, reunido em termos bastante leigos. Link: publicações.gbdirect.co.uk
c_book/chapter8/…
Eu uso-o para código livre de bloqueios / bloqueio com verificação dupla
paulm
Para mim, volatilemais útil que friendpalavra-chave.
Acegs 19/09/19
Respostas:
268
volatile é necessário se você estiver lendo de um ponto na memória que, digamos, um processo / dispositivo completamente separado / o que quer que seja gravado.
Eu costumava trabalhar com ram de porta dupla em um sistema multiprocessador em linha reta C. Usamos um valor de 16 bits gerenciado por hardware como um semáforo para saber quando o outro cara estava pronto. Essencialmente, fizemos isso:
void waitForSemaphore(){volatileuint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/while((*semPtr)!= IS_OK_FOR_ME_TO_PROCEED);}
Sem ele volatile, o otimizador vê o loop como inútil (o cara nunca define o valor! Ele é louco, se livre desse código!) E meu código continuaria sem ter adquirido o semáforo, causando problemas mais tarde.
Nesse caso, o que aconteceria se uint16_t* volatile semPtrfosse escrito? Isso deve marcar o ponteiro como volátil (em vez do valor apontado), para que as verificações no próprio ponteiro, por exemplo, semPtr == SOME_ADDRnão sejam otimizadas. No entanto, isso implica novamente em um valor pontual volátil. Não?
Zyl
@Zyl Não, isso não acontece. Na prática, o que você sugere é provavelmente o que acontecerá. Mas teoricamente, pode-se acabar com um compilador que otimiza o acesso a valores porque decidiu que nenhum desses valores é alterado. E se você quisesse aplicar volátil ao valor e não ao ponteiro, estaria ferrado. Mais uma vez, improvável, mas é melhor errar ao fazer as coisas certas, do que tirar proveito do comportamento que acontece hoje em dia.
@curiousguy não tomou uma decisão errada. Ele fez a dedução correta com base nas informações fornecidas. Se você não conseguir marcar algo volátil, o compilador poderá assumir que não é volátil . É isso que o compilador faz ao otimizar o código. Se houver mais informações, a saber, que esses dados são de fato voláteis, é responsabilidade do programador fornecer essas informações. O que você está reivindicando por um compilador de buggy é realmente apenas uma programação ruim.
Iheanyi 17/07/19
1
@curiousguy não, só porque a palavra-chave volátil aparece uma vez não significa que tudo se torna volátil de repente. Eu dei um cenário em que o compilador faz a coisa certa e alcança um resultado contrário ao que o programador espera erroneamente. Assim como a "análise mais irritante" não é o sinal do erro do compilador, nem é o caso aqui.
Iheanyi 17/07/19
82
volatileé necessário ao desenvolver sistemas embarcados ou drivers de dispositivo, nos quais é necessário ler ou gravar um dispositivo de hardware mapeado na memória. O conteúdo de um registro de dispositivo específico pode mudar a qualquer momento; portanto, você precisa da volatilepalavra-chave para garantir que esses acessos não sejam otimizados pelo compilador.
Isso não é válido apenas para sistemas embarcados, mas para o desenvolvimento de todos os drivers de dispositivo.
Mladen Janković
A única vez que eu precisava em um barramento de 8 bits ISA onde você lê o mesmo endereço duas vezes - o compilador tinha um bug e ignorou (início Zortech c ++)
Martin Beckett
Muito raramente, o volátil é adequado para o controle de dispositivos externos. Sua semântica está errada para o MMIO moderno: você precisa tornar muitos objetos voláteis e prejudicar a otimização. Mas o MMIO moderno se comporta como a memória normal até que um sinalizador seja definido, de modo que a volatilidade não deve ser necessária. Muitos motoristas nunca usam voláteis.
curiousguy
69
Alguns processadores possuem registros de ponto flutuante com mais de 64 bits de precisão (por exemplo, x86 de 32 bits sem SSE, consulte o comentário de Peter). Dessa forma, se você executar várias operações em números de precisão dupla, obterá uma resposta de precisão mais alta do que se você tivesse que truncar cada resultado intermediário para 64 bits.
Isso geralmente é ótimo, mas significa que, dependendo de como o compilador atribuiu registra e fez otimizações, você terá resultados diferentes para as mesmas operações exatamente nas mesmas entradas. Se você precisar de consistência, poderá forçar cada operação a voltar à memória usando a palavra-chave volátil.
Também é útil para alguns algoritmos que não fazem sentido algébrico, mas reduzem o erro de ponto flutuante, como a soma de Kahan. Algebricamente, é um nop, então muitas vezes é otimizado incorretamente, a menos que algumas variáveis intermediárias sejam voláteis.
Quando você calcula derivadas numéricas, também é útil garantir que x + h - x == h defina hh = x + h - x como volátil para que um delta adequado possa ser calculado.
Alexandre C.
5
+1, na verdade, na minha experiência, houve um caso em que os cálculos de ponto flutuante produziram resultados diferentes no Debug and Release, portanto os testes de unidade escritos para uma configuração estavam falhando para outra. Resolvemos isso declarando uma variável de ponto flutuante como alternativa ao volatile doubleinvés de justa double, para garantir que ela seja truncada da precisão da FPU para a precisão de 64 bits (RAM) antes de continuar os cálculos. Os resultados foram substancialmente diferentes devido a um exagero adicional do erro de ponto flutuante.
Serge Rogatch
Sua definição de "moderno" está um pouco errada. Somente o código x86 de 32 bits que evita o SSE / SSE2 é afetado por isso, e ele não era "moderno" mesmo há 10 anos. Todos os MIPS / ARM / POWER possuem registros de hardware de 64 bits, e o x86 com SSE2. As implementações em C ++ x86-64 sempre usam SSE2, e os compiladores têm opções como g++ -mfpmath=sseusá-lo também para x86 de 32 bits. Você pode usar gcc -ffloat-storepara forçar o arredondamento em qualquer lugar, mesmo ao usar o x87, ou pode definir a precisão do x87 para mantissa de 53 bits: randomascii.wordpress.com/2012/03/21/… .
Peter Cordes
Mas ainda é uma boa resposta, para o obsoleto x87 code-gen, você pode usar volatilepara forçar o arredondamento em alguns lugares específicos sem perder os benefícios em todos os lugares.
Peter Cordes
1
Ou confundo impreciso com inconsistente?
Chipster
49
De um artigo "Volátil como promessa" de Dan Saks:
(...) um objeto volátil é aquele cujo valor pode mudar espontaneamente. Ou seja, quando você declara um objeto volátil, está dizendo ao compilador que o objeto pode mudar de estado mesmo que nenhuma instrução no programa pareça alterá-lo. "
Aqui estão os links para três de seus artigos sobre a volatilepalavra-chave:
Você DEVE usar volátil ao implementar estruturas de dados sem bloqueio. Caso contrário, o compilador é livre para otimizar o acesso à variável, o que alterará a semântica.
Em outras palavras, volátil diz ao compilador que o acesso a essa variável deve corresponder a uma operação de leitura / gravação na memória física.
Por exemplo, é assim que InterlockedIncrement é declarado na API do Win32:
LONG __cdecl InterlockedIncrement(
__inout LONG volatile*Addend);
Você absolutamente NÃO precisa declarar uma variável volátil para poder usar o InterlockedIncrement.
precisa
Esta resposta está obsoleta agora que o C ++ 11 fornece std::atomic<LONG>para que você possa escrever código sem bloqueio com mais segurança, sem problemas de ter cargas / armazenamentos puros otimizados, reordenados ou qualquer outra coisa.
Peter Cordes
10
Um aplicativo grande que eu costumava trabalhar no início dos anos 90 continha o tratamento de exceções baseado em C usando setjmp e longjmp. A palavra-chave volátil era necessária em variáveis cujos valores precisavam ser preservados no bloco de código que servia como cláusula "catch", para que esses vars não fossem armazenados em registradores e eliminados pelo longjmp.
No padrão C, um dos locais a ser usado volatileé com um manipulador de sinal. De fato, no Padrão C, tudo o que você pode fazer com segurança em um manipulador de sinal é modificar uma volatile sig_atomic_tvariável ou sair rapidamente. De fato, AFAIK, é o único local no Padrão C em que o uso volatileé necessário para evitar comportamentos indefinidos.
ISO / IEC 9899: 2011 §7.14.1.1 O signal função
¶5 Se o sinal ocorrer diferente de como resultado da chamada da função abortou raise, o comportamento será indefinido se o manipulador de sinal se referir a qualquer objeto com duração de armazenamento estático ou de encadeamento que não seja um objeto atômico sem bloqueio, além de atribuir um valor para um objeto declarado como volatile sig_atomic_tou o manipulador de sinal chama qualquer função na biblioteca padrão que não seja a abortfunção, a _Exitfunção, a
quick_exitfunção ou a signalfunção com o primeiro argumento igual ao número do sinal correspondente ao sinal que causou a chamada do manipulador. Além disso, se essa chamada para a signalfunção resultar em um retorno SIG_ERR, o valor de errnoé indeterminado. 252)
252) Se algum sinal for gerado por um manipulador de sinal assíncrono, o comportamento será indefinido.
Isso significa que, no padrão C, você pode escrever:
O POSIX é muito mais tolerante com o que você pode fazer em um manipulador de sinal, mas ainda existem limitações (e uma das limitações é que a biblioteca de E / S padrão - printf()et al - não pode ser usada com segurança).
Desenvolvendo para um incorporado, tenho um loop que verifica uma variável que pode ser alterada em um manipulador de interrupções. Sem "volátil", o loop torna-se noop - até onde o compilador pode perceber, a variável nunca muda, então otimiza a verificação.
O mesmo se aplicaria a uma variável que pode ser alterada em um encadeamento diferente em um ambiente mais tradicional, mas muitas vezes fazemos chamadas de sincronização, para que o compilador não seja tão livre com a otimização.
Além de usá-lo como pretendido, o volátil é usado na metaprogramação (modelo). Ele pode ser usado para evitar sobrecarga acidental, pois o atributo volátil (como const) participa da resolução de sobrecarga.
Isso é legal; ambas as sobrecargas são potencialmente exigíveis e fazem quase o mesmo. O elenco da volatilesobrecarga é legal, pois sabemos que a barra não passará de maneira não volátil T. A volatileversão é estritamente pior, no entanto, portanto, nunca escolhida na resolução de sobrecarga se o não volátil festiver disponível.
Observe que o código nunca realmente depende do volatileacesso à memória.
Você poderia elaborar isso com um exemplo? Isso realmente me ajudaria a entender melhor. Obrigado!
batbrat
" O elenco na sobrecarga volátil " Um elenco é uma conversão explícita. É uma construção SYNTAX. Muitas pessoas fazem essa confusão (mesmo autores comuns).
curiousguy
6
você deve usá-lo para implementar spinlocks, bem como algumas (todos?) estruturas de dados sem bloqueio
use-o com operações / instruções atômicas
me ajudou uma vez a superar o bug do compilador (código gerado incorretamente durante a otimização)
É melhor usar uma biblioteca, intrínsecas do compilador ou código de montagem embutido. Volátil não é confiável.
Zan Lynx
1
1 e 2 usam operações atômicas, mas o volátil não fornece semântica atômica e as implementações específicas da plataforma do atômico substituem a necessidade do uso do volátil. Portanto, para 1 e 2, discordo, você NÃO precisa do volátil.
Quem diz alguma coisa sobre o fornecimento volátil de semântica atômica? Eu disse que você precisa usar volátil, com operações atômicas e se você não acha que é verdade olhada nas declarações de operações interligadas de win32 API (esse cara também explicou isso em sua resposta)
Mladen Janković
4
o volatile palavra-chave visa impedir que o compilador aplique otimizações em objetos que podem ser alterados de maneiras que não podem ser determinadas pelo compilador.
Objetos declarados como volatileomitidos da otimização porque seus valores podem ser alterados por código fora do escopo do código atual a qualquer momento. O sistema sempre lê o valor atual de um volatileobjeto a partir da localização da memória, em vez de manter seu valor no registro temporário no momento em que é solicitado, mesmo que uma instrução anterior solicite um valor para o mesmo objeto.
Considere os seguintes casos
1) Variáveis globais modificadas por uma rotina de serviço de interrupção fora do escopo.
2) Variáveis globais em um aplicativo multiencadeado.
Se não usarmos qualificador volátil, os seguintes problemas podem surgir
1) O código pode não funcionar como esperado quando a otimização está ativada.
2) O código pode não funcionar como esperado quando as interrupções são ativadas e usadas.
O link que você postou está extremamente desatualizado e não reflete as práticas recomendadas atuais.
precisa saber é o seguinte
2
Além do fato de a palavra-chave volátil ser usada para dizer ao compilador para não otimizar o acesso a alguma variável (que pode ser modificada por um thread ou por uma rotina de interrupção), ela também pode ser usada para remover alguns erros do compilador - SIM, pode estar ---.
Por exemplo, trabalhei em uma plataforma incorporada onde o compilador estava fazendo algumas suposições erradas sobre o valor de uma variável. Se o código não fosse otimizado, o programa funcionaria bem. Com as otimizações (que eram realmente necessárias porque era uma rotina crítica), o código não funcionava corretamente. A única solução (embora não muito correta) foi declarar a variável 'defeituosa' como volátil.
É uma suposição incorreta a ideia de que o compilador não otimiza o acesso aos voláteis. O padrão não sabe nada sobre otimizações. O compilador é obrigado a respeitar o que o padrão determina, mas é livre para fazer otimizações que não interfiram no comportamento normal.
Terminus
3
Da minha experiência, 99,9% de todos os "bugs" de otimização no braço do gcc são erros por parte do programador. Não faço ideia se isso se aplica a esta resposta. Apenas um discurso retórico sobre o tópico geral
odinthenerd
@Terminus " É uma suposição incorreta que o compilador não otimiza o acesso aos voláteis " Fonte?
precisa
2
Seu programa parece funcionar mesmo sem volatilepalavra-chave? Talvez seja por isso:
Como mencionado anteriormente, a volatilepalavra - chave ajuda em casos como
volatileint* p =...;// point to some memorywhile(*p!=0){}// loop until the memory becomes zero
Mas parece haver quase nenhum efeito quando uma função externa ou não-inline está sendo chamada. Por exemplo:
while(*p!=0){ g();}
Então, com ou sem volatilequase o mesmo resultado é gerado.
Desde que g () possa ser completamente incorporado, o compilador pode ver tudo o que está acontecendo e, portanto, pode otimizar. Mas quando o programa faz uma chamada para um local onde o compilador não pode ver o que está acontecendo, não é seguro para o compilador fazer mais suposições. Portanto, o compilador irá gerar código que sempre lê diretamente da memória.
Mas tome cuidado com o dia, quando sua função g () se tornar inline (devido a alterações explícitas ou devido à inteligência do compilador / vinculador), seu código poderá ser quebrado se você esquecer a volatilepalavra - chave!
Portanto, recomendo adicionar a volatilepalavra - chave mesmo que seu programa pareça funcionar sem. Torna a intenção mais clara e robusta em relação a mudanças futuras.
Observe que uma função pode ter seu código embutido enquanto ainda gera uma referência (resolvida no momento do link) à função de estrutura de tópicos; este será o caso de uma função recursiva parcialmente embutida. Uma função também pode ter sua semântica "incorporada" pelo compilador, ou seja, o compilador assume que os efeitos colaterais e o resultado estão dentro dos possíveis efeitos colaterais e resultados possíveis de acordo com seu código-fonte, embora ainda não o inclua. Isso se baseia na "Regra de Uma Definição eficaz", que afirma que todas as definições de uma entidade devem ser efetivamente equivalentes (se não exatamente idênticas).
curiousguy
Evitar de maneira portável a inclusão de uma chamada (ou "inclusão de sua semântica) por uma função cujo corpo é visível pelo compilador (mesmo no tempo do link com otimização global) é possível usando um volatileponteiro de função qualificado:void (* volatile fun_ptr)() = fun; fun_ptr();
curiousguy
2
Nos primeiros dias de C, os compiladores interpretavam todas as ações que lêem e gravam lvalues como operações de memória, a serem executadas na mesma sequência em que as leituras e gravações aparecem no código. A eficiência poderia ser bastante aprimorada em muitos casos, se os compiladores tivessem uma certa liberdade de reordenar e consolidar operações, mas havia um problema com isso. Mesmo as operações eram frequentemente especificadas em uma determinada ordem, apenas porque era necessário especificá-las em alguma ordem, e assim o programador escolheu uma das muitas alternativas igualmente boas, que nem sempre era o caso. Às vezes, é importante que certas operações ocorram em uma sequência específica.
Exatamente quais detalhes do seqüenciamento são importantes variarão, dependendo da plataforma de destino e do campo do aplicativo. Em vez de fornecer um controle particularmente detalhado, o Padrão optou por um modelo simples: se uma sequência de acessos for feita com lvalues que não são qualificados volatile, um compilador poderá reordená-lo e consolidá-lo como achar melhor. Se uma ação for realizada com um volatilelvalue qualificado, uma implementação de qualidade deve oferecer quaisquer garantias adicionais de pedidos que possam ser necessárias pelo código direcionado à plataforma e ao campo de aplicação pretendidos, sem a necessidade de usar a sintaxe não padrão.
Infelizmente, em vez de identificar quais garantias os programadores precisariam, muitos compiladores optaram por oferecer as mínimas garantias mínimas exigidas pelo Padrão. Isso torna volatilemuito menos útil do que deveria ser. No gcc ou clang, por exemplo, um programador que precise implementar um "mutex de transferência" básico [aquele em que uma tarefa que adquiriu e liberou um mutex não fará isso novamente até que a outra tarefa o faça] deve fazer um de quatro coisas:
Coloque a aquisição e liberação do mutex em uma função que o compilador não pode incorporar e na qual não pode aplicar a Otimização de Programa Inteiro.
Qualifique todos os objetos guardados pelo mutex como - volatilealgo que não seria necessário se todos os acessos ocorrerem após a aquisição do mutex e antes de liberá-lo.
Use o nível de otimização 0 para forçar o compilador a gerar código como se todos os objetos não qualificados registerfossem volatile.
Use diretivas específicas do gcc.
Por outro lado, ao usar um compilador de alta qualidade, mais adequado para a programação de sistemas, como o icc, um teria outra opção:
Certifique-se de que uma volatilegravação qualificada seja executada em qualquer lugar em que uma aquisição ou liberação seja necessária.
A aquisição de um "mutex hand-off" básico requer uma volatileleitura (para ver se está pronta) e também não deve exigir volatilegravação (o outro lado não tentará readquiri-lo até que seja devolvido), mas é necessário executar uma volatilegravação sem sentido ainda é melhor do que qualquer uma das opções disponíveis no gcc ou clang.
Um uso que devo lembrar é que, na função manipulador de sinal, se você deseja acessar / modificar uma variável global (por exemplo, marque-a como exit = true), você deve declarar essa variável como 'volátil'.
Todas as respostas são excelentes. Mas, além disso, gostaria de compartilhar um exemplo.
Abaixo está um pequeno programa 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--;return0;}
Agora, vamos gerar o assembly do código acima (e colarei apenas as partes do assembly relevantes aqui):
O comando para gerar a montagem:
g++-S -O3 -c -fverbose-asm-Wa,-adhln assembly.cpp
E a assembléia:
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
Você pode ver no assembly para o qual o código do assembly não foi gerado sprintfporque o compilador supôs que xnão mudaria fora do programa. E o mesmo é o caso com o whileloop. whileO loop foi completamente removido devido à otimização, porque o compilador o viu como um código inútil e, portanto, diretamente atribuído5 a x(consultemovl $5, x(%rip) ).
O problema ocorre quando e se um processo / hardware externo alterasse o valor de xalgum lugar entre x = 8;eif(x == 8) . Esperamos que o elsebloco funcione, mas infelizmente o compilador cortou essa parte.
Agora, a fim de resolver isso, na assembly.cpp, vamos mudar int x;para volatile int x;e rapidamente ver o código assembly gerado:
Aqui você pode ver que os códigos de montagem para sprintf, printfe whileciclo foram gerados. A vantagem é que, se a xvariável for alterada por algum programa ou hardware externo, sprintfparte do código será executada. Da mesma forma, o whileloop pode ser usado para ocupar a espera agora.
Outras respostas já mencionam evitar alguma otimização para:
use registros mapeados na memória (ou "MMIO")
escrever drivers de dispositivo
permite a depuração mais fácil de programas
tornar os cálculos de ponto flutuante mais determinísticos
O volátil é essencial sempre que você precisar que um valor pareça vir de fora e ser imprevisível e evitar otimizações do compilador com base em um valor que é conhecido e quando um resultado não for realmente usado, mas você precisar que ele seja computado, ou usado, mas você deseja calculá-lo várias vezes para uma referência e precisa que os cálculos iniciem e terminem em pontos precisos.
Uma leitura volátil é como uma operação de entrada (como scanfou uso de cin): o valor parece vir de fora do programa, portanto, qualquer cálculo que dependa do valor precisa iniciar depois dele .
Uma gravação volátil é como uma operação de saída (como printfuma utilização de cout): o valor parece ser comunicado fora do programa; portanto, se o valor depender de uma computação, ele precisará ser concluído antes .
Portanto, um par de leitura / gravação volátil pode ser usado para domar benchmarks e tornar a medição de tempo significativa .
Sem volátil, seu cálculo poderia ser iniciado pelo compilador antes, pois nada impediria a reordenação de cálculos com funções como medição de tempo .
volatile
pode ser usado de maneira eficaz, reunido em termos bastante leigos. Link: publicações.gbdirect.co.ukvolatile
mais útil quefriend
palavra-chave.Respostas:
volatile
é necessário se você estiver lendo de um ponto na memória que, digamos, um processo / dispositivo completamente separado / o que quer que seja gravado.Eu costumava trabalhar com ram de porta dupla em um sistema multiprocessador em linha reta C. Usamos um valor de 16 bits gerenciado por hardware como um semáforo para saber quando o outro cara estava pronto. Essencialmente, fizemos isso:
Sem ele
volatile
, o otimizador vê o loop como inútil (o cara nunca define o valor! Ele é louco, se livre desse código!) E meu código continuaria sem ter adquirido o semáforo, causando problemas mais tarde.fonte
uint16_t* volatile semPtr
fosse escrito? Isso deve marcar o ponteiro como volátil (em vez do valor apontado), para que as verificações no próprio ponteiro, por exemplo,semPtr == SOME_ADDR
não sejam otimizadas. No entanto, isso implica novamente em um valor pontual volátil. Não?volatile
é necessário ao desenvolver sistemas embarcados ou drivers de dispositivo, nos quais é necessário ler ou gravar um dispositivo de hardware mapeado na memória. O conteúdo de um registro de dispositivo específico pode mudar a qualquer momento; portanto, você precisa davolatile
palavra-chave para garantir que esses acessos não sejam otimizados pelo compilador.fonte
Alguns processadores possuem registros de ponto flutuante com mais de 64 bits de precisão (por exemplo, x86 de 32 bits sem SSE, consulte o comentário de Peter). Dessa forma, se você executar várias operações em números de precisão dupla, obterá uma resposta de precisão mais alta do que se você tivesse que truncar cada resultado intermediário para 64 bits.
Isso geralmente é ótimo, mas significa que, dependendo de como o compilador atribuiu registra e fez otimizações, você terá resultados diferentes para as mesmas operações exatamente nas mesmas entradas. Se você precisar de consistência, poderá forçar cada operação a voltar à memória usando a palavra-chave volátil.
Também é útil para alguns algoritmos que não fazem sentido algébrico, mas reduzem o erro de ponto flutuante, como a soma de Kahan. Algebricamente, é um nop, então muitas vezes é otimizado incorretamente, a menos que algumas variáveis intermediárias sejam voláteis.
fonte
volatile double
invés de justadouble
, para garantir que ela seja truncada da precisão da FPU para a precisão de 64 bits (RAM) antes de continuar os cálculos. Os resultados foram substancialmente diferentes devido a um exagero adicional do erro de ponto flutuante.g++ -mfpmath=sse
usá-lo também para x86 de 32 bits. Você pode usargcc -ffloat-store
para forçar o arredondamento em qualquer lugar, mesmo ao usar o x87, ou pode definir a precisão do x87 para mantissa de 53 bits: randomascii.wordpress.com/2012/03/21/… .volatile
para forçar o arredondamento em alguns lugares específicos sem perder os benefícios em todos os lugares.De um artigo "Volátil como promessa" de Dan Saks:
Aqui estão os links para três de seus artigos sobre a
volatile
palavra-chave:fonte
Você DEVE usar volátil ao implementar estruturas de dados sem bloqueio. Caso contrário, o compilador é livre para otimizar o acesso à variável, o que alterará a semântica.
Em outras palavras, volátil diz ao compilador que o acesso a essa variável deve corresponder a uma operação de leitura / gravação na memória física.
Por exemplo, é assim que InterlockedIncrement é declarado na API do Win32:
fonte
std::atomic<LONG>
para que você possa escrever código sem bloqueio com mais segurança, sem problemas de ter cargas / armazenamentos puros otimizados, reordenados ou qualquer outra coisa.Um aplicativo grande que eu costumava trabalhar no início dos anos 90 continha o tratamento de exceções baseado em C usando setjmp e longjmp. A palavra-chave volátil era necessária em variáveis cujos valores precisavam ser preservados no bloco de código que servia como cláusula "catch", para que esses vars não fossem armazenados em registradores e eliminados pelo longjmp.
fonte
No padrão C, um dos locais a ser usado
volatile
é com um manipulador de sinal. De fato, no Padrão C, tudo o que você pode fazer com segurança em um manipulador de sinal é modificar umavolatile sig_atomic_t
variável ou sair rapidamente. De fato, AFAIK, é o único local no Padrão C em que o usovolatile
é necessário para evitar comportamentos indefinidos.Isso significa que, no padrão C, você pode escrever:
e não muito mais.
O POSIX é muito mais tolerante com o que você pode fazer em um manipulador de sinal, mas ainda existem limitações (e uma das limitações é que a biblioteca de E / S padrão -
printf()
et al - não pode ser usada com segurança).fonte
Desenvolvendo para um incorporado, tenho um loop que verifica uma variável que pode ser alterada em um manipulador de interrupções. Sem "volátil", o loop torna-se noop - até onde o compilador pode perceber, a variável nunca muda, então otimiza a verificação.
O mesmo se aplicaria a uma variável que pode ser alterada em um encadeamento diferente em um ambiente mais tradicional, mas muitas vezes fazemos chamadas de sincronização, para que o compilador não seja tão livre com a otimização.
fonte
Eu o usei nas compilações de depuração quando o compilador insiste em otimizar uma variável que eu quero poder ver enquanto passo o código.
fonte
Além de usá-lo como pretendido, o volátil é usado na metaprogramação (modelo). Ele pode ser usado para evitar sobrecarga acidental, pois o atributo volátil (como const) participa da resolução de sobrecarga.
Isso é legal; ambas as sobrecargas são potencialmente exigíveis e fazem quase o mesmo. O elenco da
volatile
sobrecarga é legal, pois sabemos que a barra não passará de maneira não volátilT
. Avolatile
versão é estritamente pior, no entanto, portanto, nunca escolhida na resolução de sobrecarga se o não volátilf
estiver disponível.Observe que o código nunca realmente depende do
volatile
acesso à memória.fonte
fonte
o
volatile
palavra-chave visa impedir que o compilador aplique otimizações em objetos que podem ser alterados de maneiras que não podem ser determinadas pelo compilador.Objetos declarados como
volatile
omitidos da otimização porque seus valores podem ser alterados por código fora do escopo do código atual a qualquer momento. O sistema sempre lê o valor atual de umvolatile
objeto a partir da localização da memória, em vez de manter seu valor no registro temporário no momento em que é solicitado, mesmo que uma instrução anterior solicite um valor para o mesmo objeto.Considere os seguintes casos
1) Variáveis globais modificadas por uma rotina de serviço de interrupção fora do escopo.
2) Variáveis globais em um aplicativo multiencadeado.
Se não usarmos qualificador volátil, os seguintes problemas podem surgir
1) O código pode não funcionar como esperado quando a otimização está ativada.
2) O código pode não funcionar como esperado quando as interrupções são ativadas e usadas.
Volátil: o melhor amigo de um programador
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
fonte
Além do fato de a palavra-chave volátil ser usada para dizer ao compilador para não otimizar o acesso a alguma variável (que pode ser modificada por um thread ou por uma rotina de interrupção), ela também pode ser usada para remover alguns erros do compilador - SIM, pode estar ---.
Por exemplo, trabalhei em uma plataforma incorporada onde o compilador estava fazendo algumas suposições erradas sobre o valor de uma variável. Se o código não fosse otimizado, o programa funcionaria bem. Com as otimizações (que eram realmente necessárias porque era uma rotina crítica), o código não funcionava corretamente. A única solução (embora não muito correta) foi declarar a variável 'defeituosa' como volátil.
fonte
Seu programa parece funcionar mesmo sem
volatile
palavra-chave? Talvez seja por isso:Como mencionado anteriormente, a
volatile
palavra - chave ajuda em casos comoMas parece haver quase nenhum efeito quando uma função externa ou não-inline está sendo chamada. Por exemplo:
Então, com ou sem
volatile
quase o mesmo resultado é gerado.Desde que g () possa ser completamente incorporado, o compilador pode ver tudo o que está acontecendo e, portanto, pode otimizar. Mas quando o programa faz uma chamada para um local onde o compilador não pode ver o que está acontecendo, não é seguro para o compilador fazer mais suposições. Portanto, o compilador irá gerar código que sempre lê diretamente da memória.
Mas tome cuidado com o dia, quando sua função g () se tornar inline (devido a alterações explícitas ou devido à inteligência do compilador / vinculador), seu código poderá ser quebrado se você esquecer a
volatile
palavra - chave!Portanto, recomendo adicionar a
volatile
palavra - chave mesmo que seu programa pareça funcionar sem. Torna a intenção mais clara e robusta em relação a mudanças futuras.fonte
volatile
ponteiro de função qualificado:void (* volatile fun_ptr)() = fun; fun_ptr();
Nos primeiros dias de C, os compiladores interpretavam todas as ações que lêem e gravam lvalues como operações de memória, a serem executadas na mesma sequência em que as leituras e gravações aparecem no código. A eficiência poderia ser bastante aprimorada em muitos casos, se os compiladores tivessem uma certa liberdade de reordenar e consolidar operações, mas havia um problema com isso. Mesmo as operações eram frequentemente especificadas em uma determinada ordem, apenas porque era necessário especificá-las em alguma ordem, e assim o programador escolheu uma das muitas alternativas igualmente boas, que nem sempre era o caso. Às vezes, é importante que certas operações ocorram em uma sequência específica.
Exatamente quais detalhes do seqüenciamento são importantes variarão, dependendo da plataforma de destino e do campo do aplicativo. Em vez de fornecer um controle particularmente detalhado, o Padrão optou por um modelo simples: se uma sequência de acessos for feita com lvalues que não são qualificados
volatile
, um compilador poderá reordená-lo e consolidá-lo como achar melhor. Se uma ação for realizada com umvolatile
lvalue qualificado, uma implementação de qualidade deve oferecer quaisquer garantias adicionais de pedidos que possam ser necessárias pelo código direcionado à plataforma e ao campo de aplicação pretendidos, sem a necessidade de usar a sintaxe não padrão.Infelizmente, em vez de identificar quais garantias os programadores precisariam, muitos compiladores optaram por oferecer as mínimas garantias mínimas exigidas pelo Padrão. Isso torna
volatile
muito menos útil do que deveria ser. No gcc ou clang, por exemplo, um programador que precise implementar um "mutex de transferência" básico [aquele em que uma tarefa que adquiriu e liberou um mutex não fará isso novamente até que a outra tarefa o faça] deve fazer um de quatro coisas:Coloque a aquisição e liberação do mutex em uma função que o compilador não pode incorporar e na qual não pode aplicar a Otimização de Programa Inteiro.
Qualifique todos os objetos guardados pelo mutex como -
volatile
algo que não seria necessário se todos os acessos ocorrerem após a aquisição do mutex e antes de liberá-lo.Use o nível de otimização 0 para forçar o compilador a gerar código como se todos os objetos não qualificados
register
fossemvolatile
.Use diretivas específicas do gcc.
Por outro lado, ao usar um compilador de alta qualidade, mais adequado para a programação de sistemas, como o icc, um teria outra opção:
volatile
gravação qualificada seja executada em qualquer lugar em que uma aquisição ou liberação seja necessária.A aquisição de um "mutex hand-off" básico requer uma
volatile
leitura (para ver se está pronta) e também não deve exigirvolatile
gravação (o outro lado não tentará readquiri-lo até que seja devolvido), mas é necessário executar umavolatile
gravação sem sentido ainda é melhor do que qualquer uma das opções disponíveis no gcc ou clang.fonte
Um uso que devo lembrar é que, na função manipulador de sinal, se você deseja acessar / modificar uma variável global (por exemplo, marque-a como exit = true), você deve declarar essa variável como 'volátil'.
fonte
Todas as respostas são excelentes. Mas, além disso, gostaria de compartilhar um exemplo.
Abaixo está um pequeno programa cpp:
Agora, vamos gerar o assembly do código acima (e colarei apenas as partes do assembly relevantes aqui):
O comando para gerar a montagem:
E a assembléia:
Você pode ver no assembly para o qual o código do assembly não foi gerado
sprintf
porque o compilador supôs quex
não mudaria fora do programa. E o mesmo é o caso com owhile
loop.while
O loop foi completamente removido devido à otimização, porque o compilador o viu como um código inútil e, portanto, diretamente atribuído5
ax
(consultemovl $5, x(%rip)
).O problema ocorre quando e se um processo / hardware externo alterasse o valor de
x
algum lugar entrex = 8;
eif(x == 8)
. Esperamos que oelse
bloco funcione, mas infelizmente o compilador cortou essa parte.Agora, a fim de resolver isso, na
assembly.cpp
, vamos mudarint x;
paravolatile int x;
e rapidamente ver o código assembly gerado:Aqui você pode ver que os códigos de montagem para
sprintf
,printf
ewhile
ciclo foram gerados. A vantagem é que, se ax
variável for alterada por algum programa ou hardware externo,sprintf
parte do código será executada. Da mesma forma, owhile
loop pode ser usado para ocupar a espera agora.fonte
Outras respostas já mencionam evitar alguma otimização para:
O volátil é essencial sempre que você precisar que um valor pareça vir de fora e ser imprevisível e evitar otimizações do compilador com base em um valor que é conhecido e quando um resultado não for realmente usado, mas você precisar que ele seja computado, ou usado, mas você deseja calculá-lo várias vezes para uma referência e precisa que os cálculos iniciem e terminem em pontos precisos.
Uma leitura volátil é como uma operação de entrada (como
scanf
ou uso decin
): o valor parece vir de fora do programa, portanto, qualquer cálculo que dependa do valor precisa iniciar depois dele .Uma gravação volátil é como uma operação de saída (como
printf
uma utilização decout
): o valor parece ser comunicado fora do programa; portanto, se o valor depender de uma computação, ele precisará ser concluído antes .Portanto, um par de leitura / gravação volátil pode ser usado para domar benchmarks e tornar a medição de tempo significativa .
Sem volátil, seu cálculo poderia ser iniciado pelo compilador antes, pois nada impediria a reordenação de cálculos com funções como medição de tempo .
fonte