volatilevolatile修飾型は,次の文法を持ちます。
プログラムの実行とは無関係に値が変化するデータという意味なのですが,仕様書(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修飾型である必要があります。