C の揮発性修飾子
今日のチュートリアルでは、C の volatile
修飾子について説明します。C プログラミングでこの修飾子をどこでどのように使用できるかを理解します。
C の volatile
修飾子
低レベルのプログラミングに取り組むまで、volatile 修飾子は使用しません。 ここで、低レベルのプログラミングとは、ハードウェアとやり取りすることになっている IO ポートと割り込みサービス ルーチン (ISR) を処理する必要があるコードを意味します。
ソース コードがなくても実行可能ファイルを実行できるように、コンパイラが C コードをマシン コードに変換することは誰もが知っています。
他のテクノロジと同様に、C プログラミング用のコンパイラもソース コードをマシン コードに変換します。 ここで、コンパイラは通常、結果 (出力) を最適化するのに苦労するため、最後に最小限のマシン コードを実行する必要があります。
この種の最適化により、コンパイラの観点から更新されていない変数にアクセスするための不要なマシン コードが削除されます。
コード例:
int main() {
int status = 0;
while (status == 0) {
}
}
最適化コンパイラは、上記のコードの while
ループで status
という名前の変数が更新されていないことを確認します。 したがって、反復ごとにこの変数にアクセスする必要はありません。
ループはコンパイラによって無限ループ (while(1)
) に変換されるため、status
変数を読み取るためのマシン コードは必要ありません。
コンパイラは、現在のプログラムでループ外の任意の時点でも status
変数を更新できることを知りません。 たとえば、周辺機器で IO 操作が発生した場合です。
実際には、プログラムは変更していませんが、繰り返しごとにコンパイラが status
という名前の変数にアクセスできるようにする必要があります。 この種の状況を回避するために、そのような C プログラムのすべてのコンパイル最適化をオフにすることを提案する場合がありますが、次の理由により、それは解決策ではありません。
- コンパイラの実装は、それぞれ異なります。
- 1つの変数のためだけにすべてのコンパイラー最適化をオフにすると、問題が発生する可能性があります。これは、これらの最適化の一部が他のプログラムの部分で必要になる可能性があるためです。
- コンパイラの最適化がオフになっているため、低レベルのアプリケーションが想定どおりに動作しません。 たとえば、実行の遅延。
ここで volatile
修飾子が必要です。 volatile
キーワードは、(プログラマーとして) status
の最適化が許可されていないことをコンパイラーに伝えるために使用する修飾子にすぎず、次のように使用されます。
volatile int status = 0;
この修飾子を使用する場合は、次の volatile
のプロパティを考慮する必要があります。
- メモリの割り当てを削除することはできません。
- 変数はレジスタにキャッシュできません。
- 割り当ての順序で値を変更することはできません。
C プログラミングでの volatile
修飾子の使用
以下のコードでは、スレッドが done
という名前の変数を変更するまで "Waiting..."
というメッセージを出力し、次に "Okay, let's move on"
という別のメッセージを出力します。
コード例 (volatile
なし):
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
bool done = false;
void *tfunc() {
sleep(1);
done = true;
return NULL;
}
int main() {
pthread_t t1;
pthread_create(&t1, NULL, tfunc, NULL);
printf("Waiting...\n");
while (!done) {
}
printf("Okay, Let's move on");
}
このプログラムをコンパイルして実行します。
PS C:\Users\DelftStack\Desktop\C> gcc volatile.c -o volatile -lpthread
PS C:\Users\DelftStack\Desktop\C> ./volatile
次の出力を見ると、通常は期待どおりに進んでいます。
出力:
Waiting...
Okay, Let's move on
ここで、同じソース コードに対してコンパイラの最適化をオンにします。
PS C:\Users\DelftStack\Desktop\C> gcc -O3 volatile.c -o volatile -lpthread
PS C:\Users\DelftStack\Desktop\C> ./volatile
以下の出力が表示されるため、期待どおりに機能していません。
出力:
Waiting...
コンパイラは while
ループを調べて、done
変数が while
ループで更新されていないことに気付きました。 コンパイラは、別のスレッドが done
という名前のグローバル変数を変更していることを認識していないため、コンパイラはコードを変更してプログラムを中断します。
ここで volatile
修飾子を使用します。 コードを更新し、done
変数を volatile
にします。
コード例 (volatile
を使用):
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
volatile bool done = false;
void *tfunc() {
sleep(1);
done = true;
return NULL;
}
int main() {
pthread_t t1;
pthread_create(&t1, NULL, tfunc, NULL);
printf("Waiting...\n");
while (!done) {
}
printf("Okay, Let's move on");
}
最適化を行ってもコードは正常に動作します。 以下を参照してください。
PS C:\Users\DelftStack\Desktop\C> gcc -O3 volatile.c -o volatile -lpthread
PS C:\Users\DelftStack\Desktop\C> ./volatile
出力:
Waiting...
Okay, Let's move on