C言語におけるvolatileキーワードの詳細とよくある面接問題
6739 ワード
コンパイラの最適化
プログラム実行の最適化はハードウェアとソフトウェアに分けることができる.ハードウェアでは、CPUとメモリの間にcacheを追加し、CPUとメモリの間の動作速度の違いが大きすぎるという問題を解決します.ソフトウェアの上でコンパイラの最適化とプログラマーの最適化に分けます:プログラマーの最適化はプログラマーがコードを書く時、コードの論理の順序に対して合理的に手配して、効率を高めます;コンパイラ最適化はプログラマが書いたコードで、リンクをコンパイルするときにコンパイラが最適化し、コードの実行順序を調整したり、無駄な文を削除したりします.コンパイラ最適化でよく使われる方法は、メモリ変数をレジスタにキャッシュすることです.コマンドの順序を調整するにはCPUコマンドラインを十分に利用し,読み書きコマンドを並べ替えるのが一般的である.CPUとメモリの読み書き速度の差が大きすぎるため、コンパイラは中間変数や読み出したばかりのデータをcache、レジスタに存在させるなど、読み書きメモリの動作をできるだけ減らすことができます.コンパイラにはデータストリーム分析という技術があり、分析プログラムの変数がどこで付与され、どこで使用され、どこで失効するか、分析結果は定数マージ、定数伝播などの最適化に使用され、さらにいくつかのコードを除去することができ、言い換えればプログラマーが書いたコードはすべて実行されるのではなく、コンパイラが意味のないコードを削除することがある.
C言語におけるvolatileキーワードの役割 volatile int a;
main()
{
a=0;
b = a;
printf("b = %d
", b);
}
volatileは「多変」を意味し、volatileによって修飾された変数は、その変数が予想外に変更されることを示し、その変数を使用するたびに、一時的に保存された変数の値を使用しないで、その変数のメモリアドレスから読み出す.例えば、上記のコードでは、b=a文を実行すると、コンパイラは、aの付与後にaの値が変更されていないと判断するので、aのメモリアドレスからaを読み出してbに付与するのではなく、レジスタに一時保存されているaの値をbに与える.一般的にはこれで大丈夫ですが、割り込みプログラムや他のプロセスがaの値を変更した場合、レジスタに一時保存されているaはメモリに保存されているaと一致しません.volatile修飾を加えないで、コンパイラはやはりレジスタが一時的に保存したaをbに付与して、この時レジスタの中のaの値はもう最新のaの値ではなくて、付与は問題があります;volatile修飾を加えると、aを使用するたびにaのメモリアドレスにaが読み出され、読み出されるたびにaが最新の値であることが保証されるが、読み取りメモリの速度が読み取りレジスタの速度よりはるかに低いため、効率が低下する.簡単に言えば、volatile修飾を加えて、この変数を使用するたびにメモリアドレスに読み取り、プログラマーが書いた各行のコードは実行され、コンパイラが最適化しないでください.コンパイラは最適化時にコンパイラが無駄だと思っているコードを削除するからです.
volatileキーワードの適用例:
1.割り込みサービスプログラムで変更された変数値は、他のプログラムが検出する状態値である
static int status;
int main(void)
{
while (1)
{
if (status)
{
printf("status = 1
");
status = 0;
}
}
}
//
void ISR_fun(void)
{
status=1;
}
コード解析:上記のプログラムでは、サービスを中断するプログラムがstatusの値を変更し、メインプログラムに「status=1」を印刷させることを目的としています.メインプログラムはサービスプログラムを中断してstatusの値を修正することを知らないで、コンパイラの最適化を経て、メインプログラムが初めてstatusの値を読み取るのはメモリから読み取って、後でstatusの値を使う時すべて使う前に読み取ったものです;その割り込みサービスプログラムはstatusの値を実行して変更しても、メインプログラムはメモリに読み込まれず、メインプログラムではstatusの値は永遠に変わらず、プログラムがエラーになります.volatile修飾status変数を加えると、メインプログラムはstatus値を使用するたびにメモリアドレスに読み取り、サービスプログラムを中断してstatusの値を変更した後、本当に機能することを保証します.
2.ハードウェアデバイスの一部のレジスタを操作する場合、volatile修飾を追加する必要があります。
volatile int *sequenceInit = (unsigned int *)0xfffff000;//
int init(void)
{
int i;
for(i=0;i< 10;i++)
{
*sequenceInit = i;
}
}
コード解析:ARMチップは統合アドレッシングであり、操作レジスタは読み出しメモリアドレスと同じであり、上記のプログラムがARMチップによるメモリの初期化コードであると仮定する.ARMチップ初期化メモリとは、メモリコントローラを操作してメモリを初期化し、データマニュアルの初期化説明に従ってメモリコントローラのレジスタに特定の値を順次書き込むことです.forループがシーケンスの初期化であると仮定し、アドレス0 xfffff 000のレジスタに0〜9を順次書き込む.volatile修飾を加えないと、コンパイラはレジスタに0から9を順次書き込むのと直接9を書き込むのと同じ効果があると考え、forサイクル全体がsequenceInit=9に置き換えられ、メモリを初期化できないことが明らかになり、初期化に失敗します.以上の解析から,このときのsequenceInitはvolatile修飾を加えなければならないことが分かった.
volatileでよくある面接の質問:
1.パラメータはconstでもvolatileでもいいですか?
例えば、読み取り専用のステータスレジスタであってもよい.それはvolatileです.予想外に変わる可能性があるからです.これはconstです.プログラムはそれを修正しようとしないからです.
2.ポインタはvolatileですか?
はい、サービスサブルーチンがbufferを指すポインタを修理する場合です.ポインタは通常の変数であり、アクセス上他の変数と異なる特性はありません.保存されている数値は整形データであり、整数変数とは異なり、この整数データはメモリアドレスを指しています.
volatile int a;
main()
{
a=0;
b = a;
printf("b = %d
", b);
}
volatileは「多変」を意味し、volatileによって修飾された変数は、その変数が予想外に変更されることを示し、その変数を使用するたびに、一時的に保存された変数の値を使用しないで、その変数のメモリアドレスから読み出す.例えば、上記のコードでは、b=a文を実行すると、コンパイラは、aの付与後にaの値が変更されていないと判断するので、aのメモリアドレスからaを読み出してbに付与するのではなく、レジスタに一時保存されているaの値をbに与える.一般的にはこれで大丈夫ですが、割り込みプログラムや他のプロセスがaの値を変更した場合、レジスタに一時保存されているaはメモリに保存されているaと一致しません.volatile修飾を加えないで、コンパイラはやはりレジスタが一時的に保存したaをbに付与して、この時レジスタの中のaの値はもう最新のaの値ではなくて、付与は問題があります;volatile修飾を加えると、aを使用するたびにaのメモリアドレスにaが読み出され、読み出されるたびにaが最新の値であることが保証されるが、読み取りメモリの速度が読み取りレジスタの速度よりはるかに低いため、効率が低下する.簡単に言えば、volatile修飾を加えて、この変数を使用するたびにメモリアドレスに読み取り、プログラマーが書いた各行のコードは実行され、コンパイラが最適化しないでください.コンパイラは最適化時にコンパイラが無駄だと思っているコードを削除するからです.
volatileキーワードの適用例:
1.割り込みサービスプログラムで変更された変数値は、他のプログラムが検出する状態値である
static int status;
int main(void)
{
while (1)
{
if (status)
{
printf("status = 1
");
status = 0;
}
}
}
//
void ISR_fun(void)
{
status=1;
}
コード解析:上記のプログラムでは、サービスを中断するプログラムがstatusの値を変更し、メインプログラムに「status=1」を印刷させることを目的としています.メインプログラムはサービスプログラムを中断してstatusの値を修正することを知らないで、コンパイラの最適化を経て、メインプログラムが初めてstatusの値を読み取るのはメモリから読み取って、後でstatusの値を使う時すべて使う前に読み取ったものです;その割り込みサービスプログラムはstatusの値を実行して変更しても、メインプログラムはメモリに読み込まれず、メインプログラムではstatusの値は永遠に変わらず、プログラムがエラーになります.volatile修飾status変数を加えると、メインプログラムはstatus値を使用するたびにメモリアドレスに読み取り、サービスプログラムを中断してstatusの値を変更した後、本当に機能することを保証します.
2.ハードウェアデバイスの一部のレジスタを操作する場合、volatile修飾を追加する必要があります。
volatile int *sequenceInit = (unsigned int *)0xfffff000;//
int init(void)
{
int i;
for(i=0;i< 10;i++)
{
*sequenceInit = i;
}
}
コード解析:ARMチップは統合アドレッシングであり、操作レジスタは読み出しメモリアドレスと同じであり、上記のプログラムがARMチップによるメモリの初期化コードであると仮定する.ARMチップ初期化メモリとは、メモリコントローラを操作してメモリを初期化し、データマニュアルの初期化説明に従ってメモリコントローラのレジスタに特定の値を順次書き込むことです.forループがシーケンスの初期化であると仮定し、アドレス0 xfffff 000のレジスタに0〜9を順次書き込む.volatile修飾を加えないと、コンパイラはレジスタに0から9を順次書き込むのと直接9を書き込むのと同じ効果があると考え、forサイクル全体がsequenceInit=9に置き換えられ、メモリを初期化できないことが明らかになり、初期化に失敗します.以上の解析から,このときのsequenceInitはvolatile修飾を加えなければならないことが分かった.
volatileでよくある面接の質問:
1.パラメータはconstでもvolatileでもいいですか?
例えば、読み取り専用のステータスレジスタであってもよい.それはvolatileです.予想外に変わる可能性があるからです.これはconstです.プログラムはそれを修正しようとしないからです.
2.ポインタはvolatileですか?
はい、サービスサブルーチンがbufferを指すポインタを修理する場合です.ポインタは通常の変数であり、アクセス上他の変数と異なる特性はありません.保存されている数値は整形データであり、整数変数とは異なり、この整数データはメモリアドレスを指しています.
static int status;
int main(void)
{
while (1)
{
if (status)
{
printf("status = 1
");
status = 0;
}
}
}
//
void ISR_fun(void)
{
status=1;
}
volatile int *sequenceInit = (unsigned int *)0xfffff000;//
int init(void)
{
int i;
for(i=0;i< 10;i++)
{
*sequenceInit = i;
}
}
1.パラメータはconstでもvolatileでもいいですか?
例えば、読み取り専用のステータスレジスタであってもよい.それはvolatileです.予想外に変わる可能性があるからです.これはconstです.プログラムはそれを修正しようとしないからです.
2.ポインタはvolatileですか?
はい、サービスサブルーチンがbufferを指すポインタを修理する場合です.ポインタは通常の変数であり、アクセス上他の変数と異なる特性はありません.保存されている数値は整形データであり、整数変数とは異なり、この整数データはメモリアドレスを指しています.