volatile
volatile修飾型は,次の文法を持ちます。
プログラムの実行とは無関係に値が変化するデータという意味なのですが,仕様書(C99)だけでは良く分からないので,冗長にならない程度に説明を付します。
volatile
は該当の変数に処理の最適化をしないようにコンパイラに知らせるためのものです。コンパイラは処理の最適化を試み,一部の変数を CPU のレジスタに割り当てたり,文を削除することがあります。次の例では,register
を付していないにもかかわらず,変数 i
が CPU のレジスタ(r30)に割り当てられています。
* 逆アセンブル disassemble
の結果を読むにはアセンブリ言語の知識が必要です。マニュアルは各CPUの生産元からダウンロード可能です。また,同じような最適化をすべてのコンパイラが行うとは限りません。
/* test1.c */ #include <stdio.h> int main(void) { int i; for(i = 0; i < 5; i++) printf("%d\n", i); return 0; }
% cc -O -Wall test1.c % gdb a.out GNU gdb 5.1-20020125 (Apple version gdb-213) (Wed Apr 3 04:16:48 GMT 2002) Copyright 2002 Free Software Foundation, Inc. ... (gdb) disassemble main ... 0x1e94 <main+20>: mflr r31 0x1e98 <main+24>: li r30,0 0x1e9c <main+28>: addis r3,r31,0 0x1ea0 <main+32>: addi r3,r3,216 0x1ea4 <main+36>: mr r4,r30 0x1ea8 <main+40>: bl 0x1fdc <dyld_stub_printf> 0x1eac <main+44>: addi r30,r30,1 0x1eb0 <main+48>: cmpwi r30,4 0x1eb4 <main+52>: ble+ 0x1e9c <main+28> 0x1eb8 <main+56>: li r3,0 0x1ebc <main+60>: lwz r0,88(r1) 0x1ec0 <main+64>: addi r1,r1,80 0x1ec4 <main+68>: mtlr r0 0x1ec8 <main+72>: lmw r30,-8(r1) 0x1ecc <main+76>: blr
次の例では,最適化によって while
文全体が削除されています。(i
は 0
なため,繰り返しの条件はつねに偽。コンパイラは while
文の処理が無駄であると考えたため削除。)
/* test2.c */ #include <stdio.h> int main(void) { int i = 0; while(i) { printf("%d\n", i); i = 0; } printf("%d\n", i); return 0; }
% cc -O -Wall test2.c % gdb a.out GNU gdb 5.1-20020125 (Apple version gdb-213) (Wed Apr 3 04:16:48 GMT 2002) Copyright 2002 Free Software Foundation, Inc. ... (gdb) disassemble main ... 0x1ea4 <main+20>: mflr r31 0x1ea8 <main+24>: addis r3,r31,0 0x1eac <main+28>: addi r3,r3,200 0x1eb0 <main+32>: li r4,0 0x1eb4 <main+36>: bl 0x1fdc <dyld_stub_printf> 0x1eb8 <main+40>: li r3,0 0x1ebc <main+44>: lwz r0,88(r1) 0x1ec0 <main+48>: addi r1,r1,80 0x1ec4 <main+52>: mtlr r0 0x1ec8 <main+56>: lwz r31,-4(r1) 0x1ecc <main+60>: blr
しかしながら,このような最適化は,上のようなプログラムでは,特段,問題はありません。むしろ歓迎でしょう。それではいつ volatile
を使うのでしょうか。
仕様(C99)で volatile
を付さない場合に未定義の動作となるのは次の2つです。
* これらが仕様に含まれた経緯は ISO/IEC JTC1/SC22/WG14, A draft rationale for the C99 standard (N897) が参考になります。
setjmp
があるブロック内で宣言された自動記憶域期間をもつ変数で,setjmp
と longjmp
の間に変化した非volatile修飾型変数を longjmp
後に参照する場合。[C99, 7.13.2.1]
signal
関数で指定したシグナルハンドラが volatile sig_atomic_t
型でない変数を参照する場合。[C99, 7.14.1.1]
シグナルによる割り込み(interrupt)は,プログラムの進行を遮断するため,変数の値が記憶されているか否か不明になります。volatile
を付すと,読み込み時点で値が変化することを許します。(通常は,代入演算子などでプログラムの中で値が変更され保存される。volatile
を付すと,参照するだけで値が変化することをコンパイラは考慮する。)volatile
は,プログラムの外で値が変化する変数のための型修飾子とも言えます。例えば,次のようなケースです。
volatile
const volatile
static volatile
static const volatile
マウス,プリンタ,モデムなど,デバイスのプログラムをする方には必需品です。
■非局所ジャンプ(Nonlocal Jump):setjmp
と longjmp
[C99, 7.13]
#include <setjmp.h> int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);
setjmp
は longjmp
で使われる環境 env
を保存します。型 jmp_buf
はヘッダ setjmp.h
で定義されており,保存する内容を確認できます。setjmp
は if
や switch
の選択文(selection statement)と while
や for
などの繰返し文(iteration statement)のみに使えます。longjmp
は setjmp
で保存された環境 env
を,setjmp
が呼ばれたところで復元します。setjmp
は,longjmp
から戻った場合には val
を,そうでない場合には 0
を返します。
/* Example 17.3 */ #include <stdio.h> #include <stdlib.h> #include <setjmp.h> void f(void); static jmp_buf env; int main(void) { int val = -1; int x; // volatile が必要 x = 3; if((val = setjmp(env)) != 0) { printf("val = %d (x = %d)\n", val, x); exit(0); } x = 5; printf("val = %d (x = %d)\n", val, x); f(); exit(0); } void f(void) { longjmp(env, 1); }
実行結果です。
val = 0 (x = 5) val = 1 (x = 3)
val
は setjmp
が最初に呼ばれた時点で 0
となり,longjmp
が呼ばれたところで 1
となります。env
に setjmp
が実行された時点の環境,例えば,x = 3
が保存されており,longjmp
が実行された時点での x = 5
という環境は保存されていません。longjmp
が実行された時点での x
の値を setjmp
にジャンプした後に参照するには,
volatile int x;
としなければなりません。volatile
を付さない場合,longjmp
から setjmp
にジャンプ後, x
が3なのか5なのか,動作は未定義です。
■ signal
関数と割り込み(Interrupt)[C99, 7.14]
#include <signal.h> void (*signal(int sig, void (*func)(int)))(int);
シグナル sig
を受け取ると,シグナルハンドラ func
を実行します。シグナルハンドラには,次の2つが最初から用意されています。
SIG_DFL 規定の処理 SIG_IGN シグナルを無視
また,シグナル sig
は仕様(C99)に掲載されている
SIGFPE floating-point exception SIGILL illegal instruction SIGSEGV segmentation violation
以外にも多数あります。各シグナルに対する SIG_DFL
での処理も含め,man 3 signal
で確認してください。
/* Example 17.4 */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> // sleep void sigHandler(int); int main(void) { int i; signal(SIGINT, sigHandler); for(i = 1; i < 60; i++) { printf("sleeping...(%d)\n", i); sleep(1); } exit(0); } void sigHandler(int sig) { if(sig == SIGINT) fprintf(stderr, "\nSIGINT recieved.\n"); else fprintf(stderr, "\nUnpredictable signal recieved.\n"); exit(0); }
実行例です。「コントロール+C」でシグナル SIGINT
がおきます。
% ./a.out sleeping...(1) sleeping...(2) sleeping...(3) sleeping...(4) sleeping...(5) sleeping...(6) ^C SIGINT recieved.
シグナルハンドラ sigHandler
が参照する変数は volatile修飾型である必要があります。