Ir para o conteúdo

Última atualização:2026-10-12

A palavra-chave volatile em C++

Sobre o autor:

Conta oficial do WeChat:

wechat

1.volatile

Variáveis modificadas com volatile provocam, ao serem lidas ou escritas, alguns efeitos colaterais observáveis; e esses efeitos colaterais observáveis são determinados por fatores externos ao programa.

2.Aplicações de volatile

(1) Registradores de hardware de dispositivos paralelos (como registradores de status). Suponha que se queira inicializar um dispositivo cujo certo registrador é 0xff800000.

int  *output = (unsigned  int *)0xff800000; //定义一个IO端口;  
int   init(void)  
{  
    int i;  
    for(i=0;i< 10;i++)
    {  
    *output = i;  
    }  
}

Após a otimização do compilador, ele considera que todo aquele laço é inútil, sem qualquer efeito sobre o resultado final, já que, no fim, apenas atribui o valor 9 ao ponteiro output; portanto, o código compilado equivale a:

int  init(void)  
{  
    *output = 9;  
}

Se o processo de inicialização desse dispositivo externo exigir que os valores sejam atribuídos sequencialmente como no código acima, fica claro que a otimização não atinge o objetivo. Da mesma forma, se você não escrever repetidamente nessa porta, mas ler repetidamente, o resultado é o mesmo: após a otimização, o compilador talvez execute apenas uma leitura desse endereço. Sob a ótica do código, porém, não há problema algum. É aí que se deve usar volatile para avisar o compilador de que essa variável é instável e não deve ser otimizada quando encontrada.

(2) Variáveis acessadas em uma rotina de serviço de interrupção;

static int i=0;

int main()
{
    while(1)
    {
    if(i) dosomething();
    }
}

/* Interrupt service routine */
void IRS()
{
    i=1;
}

A intenção do programa de exemplo acima é: quando ocorre uma interrupção, a rotina de serviço de interrupção IRS atende à interrupção e altera a variável do programa i, fazendo com que a função dosomething seja chamada na função main. No entanto, como o compilador percebe que i não é modificado dentro de main, ele pode executar apenas uma leitura de i para um registrador e, em seguida, cada verificação if usará apenas essa "cópia de i" no registrador, de modo que dosomething nunca seria chamada. Se a variável i for modificada com volatile, o compilador garante que as operações de leitura e escrita da variável i não sejam otimizadas, assegurando que, após i ser alterada por um programa externo, a mudança seja percebida prontamente pelo programa original.

(3) Variáveis compartilhadas por várias tarefas em aplicações multithread. Quando vários threads compartilham uma determinada variável, e o valor dessa variável pode ser alterado por algum thread, ela deve ser declarada com volatile. A função é impedir que a otimização do compilador mantenha a variável em um registrador da CPU, o que, quando um thread a altera, poderia levar a erros por falta de sincronização oportuna com os demais threads. O significado de volatile é fazer o compilador, a cada operação com a variável, buscá-la de fato da memória, em vez de usar o valor já presente no registrador. Exemplo:

volatile  bool bStop=false;  //bStop 为共享全局变量  
//第一个线程
void threadFunc1()
{
    ...
    while(!bStop){...}
}
//第二个线程终止上面的线程循环
void threadFunc2()
{
    ...
    bStop = true;
}

Para terminar o laço do primeiro thread por meio do segundo, se bStop não for definido com volatile, o laço será infinito, porque bStop já terá sido lido para um registrador e o valor de bStop nesse registrador nunca se tornará FALSE; com volatile, o programa, a cada execução, lê o valor de bStop da memória, e o laço infinito deixa de ocorrer.

Conhecer ou não os cenários de aplicação de volatile é uma forma eficaz de distinguir programadores C/C++ de desenvolvedores de sistemas embarcados: quem trabalha com embarcados lida frequentemente com hardware, interrupções, RTOS e assim por diante, todos exigindo variáveis volatile; desconhecer volatile traz desastres ao design de programas.

3.Perguntas comuns sobre volatile

As perguntas a seguir permitem verificar se um candidato realmente entende volatile. (1) Um parâmetro pode ser ao mesmo tempo const e volatile? Por quê? Pode. Um exemplo é um registrador de status somente leitura. Ele é volatile porque pode ser alterado de forma inesperada; e é const porque o programa não deve tentar modificá-lo.

(2) Um ponteiro pode ser volatile? Por quê? Pode, embora isso não seja comum. Um exemplo é quando uma rotina de serviço de interrupção modifica um ponteiro para um buffer.

(3) Qual é o erro da função abaixo?

int square(volatile int *ptr) 
{ 
return *ptr * *ptr; 
} 

Este código é um pouco perverso: sua intenção é retornar o quadrado do valor apontado pelo ponteiro ptr, mas, como ptr aponta para um parâmetro do tipo volatile, o compilador gerará algo semelhante a:

int square(volatile int *ptr) 
{ 
int a,b; 
a = *ptr; 
b = *ptr; 
return a * b; 
} 

Como o valor de *ptr pode ser alterado de forma inesperada, a e b podem ser diferentes. Em consequência, esse código pode não retornar o quadrado que você espera! O código correto é:

long square(volatile int *ptr) 
{ 
int a=*ptr; 
return a * a; 
} 

4.Uso de volatile

  • A palavra-chave volatile é um modificador de tipo; variáveis do tipo declarado com ela indicam que podem ser alteradas por fatores desconhecidos do compilador (sistema operacional, hardware, outros threads etc.). Por isso, usa-se volatile para dizer ao compilador que não deve otimizar tais objetos.

  • Variáveis declaradas com a palavra-chave volatile devem ter seu valor buscado da memória a cada acesso (variáveis não modificadas com volatile podem, devido à otimização do compilador, ter o valor lido de um registrador da CPU)

  • Um const pode ser volatile (como um registrador de status somente leitura)
  • Um ponteiro pode ser volatile

Código para estudo:

Tópicos relacionados

Comentários