Pergunta

O que faz o volatile palavra-chave fazer?Em C++, que problema isso resolve?

No meu caso, nunca precisei disso conscientemente.

Foi útil?

Solução

volatile é necessário se você estiver lendo de um ponto na memória que, digamos, um processo/dispositivo/qualquer coisa completamente separado possa gravar.

Eu costumava trabalhar com memória RAM de porta dupla em um sistema multiprocessador em C direto.Usamos um valor de 16 bits gerenciado por hardware como semáforo para saber quando o outro cara terminou.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 está maluco, livre-se desse código!) e meu código continuaria sem ter adquirido o semáforo, causando problemas mais tarde.

Outras dicas

volatile é necessário ao desenvolver sistemas embarcados ou drivers de dispositivos, onde você precisa 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, então você precisa do volatile palavra-chave para garantir que tais acessos não sejam otimizados pelo compilador.

Alguns processadores possuem registradores de ponto flutuante com mais de 64 bits de precisão (por exemplo.x86 de 32 bits sem SSE, veja 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 maior precisão do que se truncasse cada resultado intermediário para 64 bits.

Isso geralmente é ótimo, mas significa que dependendo de como o compilador atribuiu registros e fez otimizações, você terá resultados diferentes para exatamente as mesmas operações nas mesmas entradas.Se precisar de consistência, você pode forçar cada operação a voltar para a 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 o somatório de Kahan.Algebricamente é um não, por isso muitas vezes será otimizado incorretamente, a menos que algumas variáveis ​​intermediárias sejam voláteis.

A partir de um "Volátil como uma promessa" artigo de Dan Saks:

(...) um objeto volátil é aquele cujo valor pode mudar espontaneamente.Ou seja, quando você declara um objeto como volátil, você 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 links para três de seus artigos sobre o volatile palavra-chave:

Você DEVE usar volátil ao implementar estruturas de dados sem bloqueio.Caso contrário, o compilador estará livre para otimizar o acesso à variável, o que alterará a semântica.

Dito de outra forma, volátil informa ao compilador que o acesso a esta variável deve corresponder a uma operação de leitura/gravação da memória física.

Por exemplo, é assim que InterlockedIncrement é declarado na API Win32:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

Um grande aplicativo no qual trabalhei no início da década de 1990 continha 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 registros e eliminados pelo longjmp.

No Padrão C, um dos locais para usar volatile é com um manipulador de sinal.Na verdade, no Padrão C, tudo o que você pode fazer com segurança em um manipulador de sinais é modificar um volatile sig_atomic_t variável ou saia rapidamente.Na verdade, AFAIK, é o único lugar no Padrão C onde o uso de volatile é necessário para evitar comportamento indefinido.

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

¶5 Se o sinal ocorrer de outra forma que não seja o resultado da chamada do abort ou raise função, o comportamento é indefinido se o manipulador de sinal se referir a qualquer objeto com duração de armazenamento estático ou de rosca que não seja um objeto atômico sem bloqueio que não seja atribuindo um valor a 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 abort função, o _Exit função, o quick_exit função, ou o signal Função com o primeiro argumento igual ao número do sinal correspondente ao sinal que causou a invocação do manipulador.Além disso, se tal chamada para o signal a função resulta 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:

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.

POSIX é muito mais tolerante sobre o que você pode fazer em um manipulador de sinal, mas ainda existem limitações (e uma das limitações é que a biblioteca Standard I/O — printf() et al – não pode ser usado com segurança).

Desenvolvendo para um incorporado, tenho um loop que verifica uma variável que pode ser alterada em um manipulador de interrupção.Sem "volátil", o loop se torna um noop - até onde o compilador sabe, a variável nunca muda, portanto otimiza a verificação.

A mesma coisa se aplicaria a uma variável que pode ser alterada em um thread diferente em um ambiente mais tradicional, mas muitas vezes fazemos chamadas de sincronização, portanto o compilador não é tão livre com a otimização.

Eu o usei em compilações de depuração quando o compilador insiste em otimizar uma variável que desejo ver à medida que passo pelo código.

Além de usá-lo conforme pretendido, o volátil é usado na metaprogramação (modelo).Pode ser usado para evitar sobrecarga acidental, pois o atributo volátil (como const) participa da resolução da 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 no volatile sobrecarga é legal, pois sabemos que a barra não passará por um não volátil T de qualquer forma.O volatile versão é estritamente pior, portanto, nunca escolha a resolução de sobrecarga se o não volátil f está disponível.

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

  1. você deve usá-lo para implementar spinlocks, bem como algumas (todas?) estruturas de dados 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)

O volatile A palavra-chave tem como objetivo evitar que o compilador aplique quaisquer 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 volatile objeto do local de memória em vez de manter seu valor no registro temporário no ponto em que é solicitado, mesmo que uma instrução anterior tenha solicitado um valor do 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 uma aplicação multithread.

Se não usarmos o qualificador volátil, os seguintes problemas podem surgir

1) O código pode não funcionar conforme o esperado quando a otimização está ativada.

2) O código pode não funcionar conforme o esperado quando as interrupções são habilitadas e usadas.

Volátil:O melhor amigo de um programador

https://en.wikipedia.org/wiki/Volatile_(programação_de_computador)

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 uma thread ou por uma rotina de interrupção), ela também pode ser usado para remover alguns bugs do compilador -- Sim, pode ser ---.

Por exemplo, trabalhei em uma plataforma incorporada onde o compilador estava fazendo algumas suposições erradas em relação ao valor de uma variável.Se o código não fosse otimizado, o programa funcionaria bem.Com otimizações (que eram realmente necessárias por se tratar de 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.

Seu programa parece funcionar mesmo sem volatile palavra-chave?Talvez seja este o motivo:

Como mencionado anteriormente o volatile palavra-chave ajuda em casos como

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

Mas parece não haver quase nenhum efeito quando uma função externa ou não embutida é chamada.Por exemplo.:

while( *p!=0 ) { g(); }

Então com ou sem volatile quase o mesmo resultado é gerado.

Contanto que g() possa ser completamente embutido, o compilador poderá ver tudo o que está acontecendo e, portanto, otimizar.Mas quando o programa faz uma chamada para um local onde o compilador não consegue ver o que está acontecendo, não é mais seguro para o compilador fazer suposições.Conseqüentemente, o compilador irá gerar código que sempre lê diretamente da memória.

Mas cuidado com o dia, quando sua função g() ficar embutida (devido a mudanças explícitas ou devido à inteligência do compilador/vinculador), seu código poderá quebrar se você esquecer o volatile palavra-chave!

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

Nos primeiros dias de C, os compiladores interpretavam todas as ações que leem e escrevem lvalues ​​como operações de memória, a serem executadas na mesma sequência em que as leituras e escritas apareciam no código.A eficiência poderia ser muito melhorada em muitos casos se os compiladores tivessem certa liberdade para reordenar e consolidar operações, mas havia um problema com isso.Mesmo as operações eram muitas vezes especificadas numa determinada ordem apenas porque era necessário especificá-las em alguns ordem e, portanto, o programador escolheu uma das muitas alternativas igualmente boas, mas nem sempre foi esse o caso.Às vezes seria importante que certas operações ocorressem em uma sequência específica.

Exatamente quais detalhes de sequenciamento são importantes variarão dependendo da plataforma alvo e do campo de aplicação.Em vez de fornecer um controle particularmente detalhado, a Norma optou por um modelo simples:se uma sequência de acessos for feita com lvalues ​​que não são qualificados volatile, um compilador pode reordená-los e consolidá-los conforme achar adequado.Se uma ação for realizada com um volatile-qualified lvalue, uma implementação de qualidade deve oferecer quaisquer garantias adicionais de ordenação que possam ser exigidas pelo código direcionado à plataforma e ao campo de aplicação pretendidos, sem precisar exigir o uso de sintaxe não padrão.

Infelizmente, em vez de identificar quais garantias os programadores precisariam, muitos compiladores optaram por oferecer as garantias mínimas exigidas pelo Padrão.Isto faz volatile muito menos útil do que deveria ser.No gcc ou no clang, por exemplo, um programador que precisa implementar um "hand-off mutex" básico [aquele em que uma tarefa que adquiriu e liberou um mutex não o fará novamente até que a outra tarefa o tenha feito] 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 à qual não pode aplicar a otimização de todo o programa.

  2. Qualifique todos os objetos protegidos pelo mutex como volatile--algo que não deveria ser necessário se todos os acessos ocorrerem após adquirir o 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 fossem qualificados register são volatile.

  4. Use diretivas específicas do gcc.

Por outro lado, ao usar um compilador de maior qualidade e mais adequado para programação de sistemas, como o icc, teríamos outra opção:

  1. Certifique-se de que um volatileA gravação qualificada é executada sempre que uma aquisição ou liberação é necessária.

Adquirir um "mutex de transferência" básico requer um volatile leia (para ver se está pronto) e não deve exigir um volatile escrever também (o outro lado não tentará readquiri-lo até que seja devolvido), mas terá que realizar uma tarefa sem sentido volatile write ainda é melhor do que qualquer uma das opções disponíveis no gcc ou clang.

Um uso que devo lembrar é que, na função de manipulador de sinal, se você deseja acessar/modificar uma variável global (por exemplo, marcá-la como exit = true), você deve declarar essa variável como 'volátil'.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top