volatileの効果をアセンブリレベルで確認
組み込みプログラミングでは、volatile宣言が使われることがある。コンパイラの最適化を防ぐために使われる。たとえば、マイコン上の特定アドレスにデータを「確実に」書きたいときなどに使われる。
volatile宣言がない場合
test.c
void func()
{
unsigned int *reg_addr;
reg_addr = (unsigned int *)0x8000;
*reg_addr = 0x01;
*reg_addr = 0x08;
return;
}
同じ変数(*reg_addr)に続けて2回値を代入しているところがミソ。コンパイラにとっては、最初の代入には意味がない。アセンブラレベルで確認するため、デバッグ情報(-g)を追加してコンパイルする。
gcc -g -O -c test.c
コンパイルされたtest.oを逆アセンブルした結果はこちら。
$ objdump -M intel -d test.o
test.o: ファイル形式 elf64-x86-64
セクション .text の逆アセンブル:
0000000000000000 <func>:
0: f3 0f 1e fa endbr64
4: c7 04 25 00 80 00 00 mov DWORD PTR ds:0x8000,0x8
b: 08 00 00 00
f: c3 ret
予想どおり最適化されてしまって、
*reg_addr = 0x01;
に相当する部分がアセンブラレベルでは消えており、変数(*reg_addr)に最後に代入した部分(0x08を格納)のみが見える。
c7 04 25 00 80 00 00 mov DWORD PTR ds:0x8000,0x8
volatile宣言がある場合
test_volatile.c
void func()
{
volatile unsigned int *reg_addr;
reg_addr = (unsigned int *)0x8000;
*reg_addr = 0x01;
*reg_addr = 0x08;
return;
}
0x8000番地に、2回(0x01と0x08)値を書く、、、ということをコンパイラに伝える。レジスタ値を書くようなケースである。同じくデバッグ情報を追加してコンパイルする。
gcc -g -O -c test_volatile.c
コンパイルされたtest_volatile.oを逆アセンブルした結果はこちら。
$ objdump -M intel -d test_volatile.o
test_volatile.o: ファイル形式 elf64-x86-64
セクション .text の逆アセンブル:
0000000000000000 <func>:
0: f3 0f 1e fa endbr64
4: c7 04 25 00 80 00 00 mov DWORD PTR ds:0x8000,0x1
b: 01 00 00 00
f: c7 04 25 00 80 00 00 mov DWORD PTR ds:0x8000,0x8
16: 08 00 00 00
1a: c3 ret
期待どおり
*reg_addr = 0x01;
に相当する部分
c7 04 25 00 80 00 00 mov DWORD PTR ds:0x8000,0x1
が存在している。
参考サイト
たとえば、こちら。その他、「volatile」で検索すると多数見つかる。