C++でグローバル変数にvolatileの指定があったので「指定する意味あるの?」と思い、その効果を体験した。
環境: Ubuntu 22.04.2 LTS, g++ 11.3.0
ソースコード
グローバル変数を参照する無限ループを作成。
グローバル変数はシグナルハンドラで更新される。
#include <iostream>
#include <signal.h>
//volatile int a = 0; // volatile指定あり
int a = 0; // volatile指定なし
void sig_handler(int signo) // シグナルハンドラ
{
std::cout << "received signal: " << signo << "\n";
a = 1;
}
int main()
{
signal(SIGUSR1, sig_handler); // SIGUSR1受信時sig_handlerを実行
while(a == 0)
{
// 無限ループ
}
std::cout << "got out from the loop\n";
}
コンパイル
$ g++ main.cpp
実行
$ ./a.out
別のターミナルからシグナル送信
$ killall -SIGUSR1 a.out
whileループを抜けてプログラムは終了
received signal: 10
got out from the loop
$
ここまでは期待通りの動作。
最適化付きコンパイル
コンパイル時に最適化を行う。
最適化の引数は複数存在する。今回は-O1 (基本的な最適化)と-O2(ほとんどの最適化)で試す。
-O1 (基本的な最適化)でコンパイルした結果
コンパイル
$ g++ main.cpp -O1
実行
$ ./a.out
別のターミナルからシグナル送信。何度か送信。
$ killall -SIGUSR1 a.out
$ killall -SIGUSR1 a.out
$ killall -SIGUSR1 a.out
シグナルハンドラは呼ばれているが、whileループは抜けずにプログラムは終了しない。
received signal: 10
received signal: 10
received signal: 10
マジか
-O2 (ほとんどの最適化)でコンパイルした結果
コンパイル
$ g++ main.cpp -O2
実行
$ ./a.out
got out from the loop
いきなりwhileループ抜けて勝手に終了。
何じゃそりゃ。
volatileの効果の確認
最適化で散々になった結果をvolatile指定で正常になることを確認。
コードの修正: volatile付きのグローバル変数を有効化
volatile int a = 0; // volatile指定あり
//int a = 0; // volatile指定なし
コンパイル:-O1付き
$ g++ main.cpp -O1
実行
$ ./a.out
別のターミナルからシグナル送信
$ killall -SIGUSR1 a.out
whileループを抜けてプログラムは終了
received signal: 10
got out from the loop
$
期待通りの動作。
コンパイル:-O2付き
$ g++ main.cpp -O2
実行
$ ./a.out
別のターミナルからシグナル送信
$ killall -SIGUSR1 a.out
whileループを抜けてプログラムは終了
received signal: 10
got out from the loop
$
期待通りの動作。
結論
- 最適化でプログラムは期待しない動作となった。
- volatile指定で最適化で発生した問題が解消された。volatileを指定する意味があることがわかった。
めでたしめでたし