it-swarm-pt.tech

Por que existe volatilidade?

O que a palavra-chave volatile faz? Em C++, que problema ele resolve?

No meu caso, nunca precisei conscientemente disso.

201
theschmitzer

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()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Sem 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.

247
Doug T.

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 palavra-chave volatile para garantir que esses acessos não sejam otimizados pelo compilador.

78
ChrisN

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.

68
tfinniga

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 palavra-chave volatile:

45
MikeZ

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 mudará 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
);
23
Frederik Slijkerman

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 de cláusula "catch", para que esses vars não fossem armazenados em registradores e eliminados pelo longjmp.

10
Jeff Doar

No padrão C, um dos locais para usar 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 variável _volatile sig_atomic_t_ ou sair rapidamente. De fato, AFAIK, é o único local no Padrão C em que o uso de volatile é necessário para evitar comportamentos indefinidos.

ISO/IEC 9899: 2011 §7.14.1.1 A função signal

¶5 Se o sinal ocorrer diferente de como resultado da chamada da função abort ou 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, exceto atribuindo um valor para um objeto declarado como _volatile sig_atomic_t_, ou o manipulador de sinal chama qualquer função na biblioteca padrão que não seja a função abort, a função __Exit_, a função _quick_exit_ ou a função signal com o primeiro argumento igual ao número do sinal correspondente ao sinal que causou a invocação do manipulador. Além disso, se uma chamada para a função signal resultar em um retorno SIG_ERR, o valor de errno será 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:

_static volatile sig_atomic_t sig_num = 0;

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

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).

10
Jonathan Leffler

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.

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); }
};

Isso é legal; ambas as sobrecargas são potencialmente exigíveis e fazem quase o mesmo. O elenco na sobrecarga volatile é legal, pois sabemos que a barra não passará um T não volátil de qualquer maneira. A versão volatile é estritamente pior, portanto, nunca é escolhida em resolução de sobrecarga se o não volátil f estiver disponível.

Observe que o código nunca depende realmente do acesso à memória volatile.

7
MSalters

Eu o usei em compilações de depuração quando o compilador insiste em otimizar uma variável que eu quero poder ver enquanto passo o código.

7
indentation

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 dizer, 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 geralmente fazemos chamadas de sincronização, para que o compilador não seja tão livre com a otimização.

7
Arkadiy
  1. você deve usá-lo para implementar spinlocks, bem como algumas estruturas de dados (tudo?) sem bloqueio
  2. use-o com operações/instruções atômicas
  3. me ajudou uma vez a superar o bug do compilador (código gerado incorretamente durante a otimização)
6
Mladen Janković

A palavra-chave volatile tem como objetivo impedir o compilador de aplicar otimizações em objetos que podem mudar de maneiras que não podem ser determinadas pelo compilador.

Objetos declarados como volatile são 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 um objeto volatile do local da memória, em vez de manter seu valor em um 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 multithread.

Se não usarmos o 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)

4
roottraveller

Seu programa parece funcionar mesmo sem a palavra-chave volatile? Talvez seja por isso:

Como mencionado anteriormente, a palavra-chave volatile ajuda em casos como

volatile int* p = ...;  // point to some memory
while( *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 volatile, quase o mesmo resultado é gerado.

Contanto 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 palavra-chave volatile!

Portanto, recomendo adicionar a palavra-chave volatile mesmo que seu programa pareça funcionar sem. Torna a intenção mais clara e robusta em relação a mudanças futuras.

2
Joachim

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 ordem alguma, e assim o programador escolheu uma das muitas alternativas igualmente boas, que nem sempre era o caso. Às vezes, seria 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á-los e consolidá-los como entender. Se uma ação for realizada com um lvalue qualificado por volatile, uma implementação de qualidade deve oferecer quaisquer garantias de pedidos adicionais 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 no 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:

  1. 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.

  2. 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.

  3. 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 fossem volatile.

  4. 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:

  1. Certifique-se de que uma gravação qualificada em volatile seja executada em todo lugar em que uma aquisição ou liberação for necessária.

A aquisição de um "mutex hand-off" básico exige uma leitura volatile (para ver se está pronta) e não deve exigir uma gravação volatile (o outro lado não tentará adquira-o até que seja devolvido), mas ter que executar uma gravação sem sentido volatile ainda é melhor do que qualquer uma das opções disponíveis no gcc ou no clang.

2
supercat

Além do fato de que a palavra-chave volátil é 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 sada para remover alguns erros do compilador - SIM, pode ser ---.

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.

2
INS

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'.

1
bugs king