Linux講座にようこそ。このページは「C言語プログラミング入門 - 第14章.ライブラリ関数 - その他のライブラリ」です。

C言語プログラミング入門

14. ライブラリ関数(35/36) - その他のライブラリ(3/4)

14.48 非局所分岐関数

ここで説明する関数を使うと、プログラムのどこへでも自由に分岐できます。便利な反面、プログラムが非常に理解しにくいものになってしまいますので、できるだけ使わない方がよいでしょう。

14.48.1 setjmp関数

setjmp関数は非局所的なジャンプ用に実行環境(スタック・コンテキスト)を保存します。

【表14-8-7】 setjmp関数
形式#include <setjmp.h>
int setjmp(jmp_buf env);
返り値直接呼び出して実行した場合は0を返します。保存したスタック・コンテキストを使ってlongjmp関数を実行した場合はlongjmp関数で指定した値を返します。
引数
jmp_buf env
スタック・コンテキストを保存する領域を指定します。

14.48.2 longjmp関数

longjmp関数はsetjmp関数により保存した実行環境(スタック・コンテキスト)を使用して、非局所的なジャンプを行います。

【表14-8-8】 longjmp関数
形式#include <setjmp.h>
void longjmp(jmp_buf env, int val);
返り値ありません。
引数
jmp_buf env
setjmp関数でスタック・コンテキストを保存した領域を指定します。
int val
setjmp関数の返り値を指定します。0は指定できません。(指定しても1になります。)
使用法、
setjmp関数により保存したスタック・コンテキストを復元することにより、実行ステップがsetjmp関数の直前に戻ります。このことにより、setjmp関数の直前にジャンプした状態になります。

14.48.3 例題

実行時引数で指定したファイルの内容を標準出力に出力します。ただし、エラーが発生した場合はlongjmp関数によりエラー処理ステップに分岐して、メッセージを出力して終了します。

  1. #include <stdio.h>
  2. #include <setjmp.h>
  3. /* 関数プロトタイプ宣言 */
  4. void MyPrint(char *PathName, jmp_buf *Env);
  5.  
  6. int main(int argc, char **argv)
  7. {
  8.     jmp_buf env;             /* 環境保存用 */
  9.     int     jmp_return;
  10.  
  11.     if ((jmp_return = setjmp(env)) == 0)
  12.     {
  13.         /* 環境保存の為の実行 */
  14.         printf("環境を保存しました。\n");
  15.     }
  16.     else
  17.     {
  18.         /* longjmp関数による実行 */
  19.         printf("エラー発生の為、終了します。\n");
  20.         return jmp_return;
  21.     }
  22.  
  23.     if(argc == 2)
  24.     {
  25.         MyPrint(*(argv + 1), &env);
  26.     }
  27.     else
  28.     {
  29.         /* 実行時引数が不当 */
  30.         longjmp(env, 2);
  31.     }
  32.  
  33.     return 0;
  34. }
  35.  
  36. /* ファイルの内容表示関数 */
  37. void MyPrint(char *pPathName, jmp_buf *pEnv)
  38. {
  39.     FILE    *fp;
  40.     int     one_char;
  41.  
  42.     if((fp = fopen(pPathName, "r")) == NULL)
  43.     {
  44.         /* オープンが出来なかった  */
  45.         longjmp(*pEnv, 1);
  46.     }
  47.  
  48.     /* ファイルから1文字ずつ入力 */
  49.     while((one_char = fgetc(fp)) != EOF)
  50.     {
  51.         putchar(one_char);
  52.     }
  53.  
  54.     fclose(fp);
  55.  
  56.     return;
  57. }
$ ls /etc/issue aaa.txt
ls: cannot access aaa.txt: そのようなファイルやディレクトリはありません
/etc/issue
$
$ ./ex14_8_3.prg /etc/issue
環境を保存しました。
Fedora release 10 (Cambridge)
Kernel \r on an \m (\l)

$ echo $?
0 ← 正常終了です
$
$ ./ex14_8_3.prg aaa.txt
環境を保存しました。
エラー発生の為、終了します。
$ echo $?
1 ← ファイルのオープンエラーです
$
$ ./ex14_8_3.prg /etc/issue aaa.txt
環境を保存しました。
エラー発生の為、終了します。
$ echo $?
2 ← 実行時引数の指定エラーです
$
2行目
非局所分岐関数を使いますのでsetjmp.hファイルを取り込みます。
8行目
スタック・コンテキスト保存用の変数を宣言します。
11行目
setjmp関数を呼び出して実行し、その返り値を取得します。0の場合は最初の呼び出しで、0以外の場合はlongjmp関数でのジャンプによる呼び出しです。
20行目
longjmp関数でのジャンプによる実行ですので、longjmp関数により指定された返り値でプログラムを終了します。
30行目
実行時引数の数が不当のため、longjmp関数でジャンプします。setjmp関数の返り値として2を指定します。
45行目
入力ファイルがオープン出来なかったため、longjmp関数でジャンプします。setjmp関数の返り値として1を指定します。

14.49 シグナル(非同期イベント)操作関数

シグナルには終了シグナルや、キーボードからの割り込みシグナルなどがあり、それぞれデフォルトの動作が設定されています。デフォルトの動作を変更する場合は、ここで説明するsignal関数でシグナル処理関数(シグナル・ハンドラ)を登録します。ただし、シグナル操作関数はシステムコールですので、処理系および、OSのバージョン等により動作が異なりますので使用する場合は注意してください。

なお、サポートしているシグナルの種類については、kill -lコマンドで表示することが出来ます。

14.49.1 raise関数

raise関数は引数で指定されたシグナルを送信します。

【表14-8-9】 raise関数
形式#include <signal.h>
int raise(int signum);
返り値正常に処理ができた場合は0を返します。エラーの場合は0以外を返します。
引数
int signum
送信するシグナルを指定します。指定できる値はsignal.hファイルに定義されており、代表的なものとして表14-8-10のようなシグナルがあります。

シグナルは沢山あることやシステムにより異なる等のことから、ここに記述したものは代表的なもののみです。

【表14-8-10】 代表的なシグナル一覧
シグナル意味
SIGINT2キーボードからの割り込み(Interrupt)です。
SIGQUIT3キーボードによる中止(Quit)です。
SIGILL4不正な命令です。
SIGABRT6abort関数からの中断(Abort)です。
SIGFPE8浮動小数点例外です。
SIGUSR110ユーザ定義シグナル1です。
SIGSEGV11不正なメモリ参照です。
SIGUSR212ユーザ定義シグナル2です。
SIGPIPE13パイプ破壊(読み手の無いパイプへの出力)です。
SIGALRM14alarmシステムコールからのタイマーシグナルです。
SIGTERM15終了(Termination)です。
SIGCHLD17子プロセスの一旦停止(Stop)または終了です。
SIGCONT18一旦停止(Stop)からの再開です。
SIGTSTP20端末(tty)から入力された一旦停止(Stop)です。
SIGTTIN21バックグランドプロセスのtty入力です。
SIGTTOU22バックグランドプロセスのtty出力です。

14.49.2 signal関数

signal関数は指定されたシグナルに対応する、シグナル処理関数(シグナル・ハンドラ)を登録します。シグナル・ハンドラを実行すると、登録はリセットされますので同じ処理を行いたい場合は再度登録する必要があります。また、SIGKILL(強制終了)とSIGSTOP(一旦停止)シグナルは捕捉できませんし、無視することもできません。

【表14-8-11】 signal関数
形式#include <signal.h>
void ( *signal(int signum, void (*handler)(int)) ) (int);
返り値登録されていたシグナル・ハンドラの値を返します。エラーの場合はSIG_ERRを返します。
引数
int signum
シグナル・ハンドラに対応するシグナルを指定します。シグナルの種類に付いては表14-8-10を参照してください。
void (*handler)(int))
シグナル・ハンドラを指定します。ただし、特殊な値として、SIG_IGNは第1引数に指定したシグナルを無視することを、SIG_DFLはデフォルトの動作を行うことを指定します。

14.49.3 例題

SIGQUITシグナルを受信するまで「実行中!!」メッセージを表示し続けます。また、SIGINTシグナルを受信すると終了確認メッセージを出力し、「y」を入力すると終了し、それ以外を入力すると処理を続行します。なお、SIGQUITはコントロール(Ctrl)キーと「C」キーを同時に押し、SIGINTはコントロール(Ctrl)キーと「\」キーを同時に押します。

  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5.  
  6. /* 関数プロトタイプ宣言 */
  7. void SetSignal(int SignalName);
  8. void SignalHandler(int SignalName);
  9.  
  10. int main(void)
  11. {
  12.     SetSignal(SIGINT);
  13.     SetSignal(SIGQUIT);
  14.  
  15.     while(1)
  16.     {
  17.         printf("実行中!!\n");
  18.         sleep(3);
  19.     }
  20.  
  21.     return 0;
  22. }
  23.  
  24. /* シグナルの設定 */
  25. void SetSignal(int pSignalName)
  26. {
  27.     if (signal(pSignalName, SignalHandler) == SIG_ERR)
  28.     {
  29.         /* シグナル設定エラー  */
  30.         printf("シグナルの設定が出来ませんでした。終了します。\n");
  31.         exit(1);
  32.     }
  33.  
  34.     return;
  35. }
  36.  
  37. /* シグナル・ハンドラー */
  38. void SignalHandler(int pSignalName)
  39. {
  40.     char    ans;
  41.  
  42.     printf("割り込みを受け付けました。\n");
  43.  
  44.     if(pSignalName == SIGQUIT)
  45.     {
  46.         printf("直ちに終了します。\n");
  47.         exit(0);
  48.     }
  49.     else
  50.     {
  51.         printf("終了しますか?(Y/N)==> ");
  52.         scanf("%c%*c", &ans);
  53.  
  54.         if(ans == 'Y' || ans == 'y')
  55.         {
  56.             printf("直ちに終了します。\n");
  57.             exit(0);
  58.         }
  59.         else
  60.         {
  61.             /* シグナルの再設定 */
  62.             SetSignal(pSignalName);
  63.         }
  64.     }
  65.  
  66.     return;
  67. }
$ ./ex14_8_4.prg
実行中!!
実行中!!
実行中!!
^\割り込みを受け付けました。 ← SIGQUITシグナル発行
直ちに終了します。
$
$ ./ex14_8_4.prg
実行中!!
実行中!!
^C割り込みを受け付けました。 ← SIGINTシグナル発行
終了しますか?(Y/N)==> n
実行中!!
実行中!!
実行中!!
^C割り込みを受け付けました。 ← SIGINTシグナル発行
終了しますか?(Y/N)==> n
実行中!!
実行中!!
実行中!!
実行中!!
^C割り込みを受け付けました。 ← SIGINTシグナル発行
終了しますか?(Y/N)==> y
直ちに終了します。
$
2行目
シグナル操作関数を使いますのでsignal.hファイルを取り込みます。
12、13行目
SetSignal関数によりSIGINTシグナルとSIGQUITシグナルのシグナル・ハンドラを登録します。
27行目
signal関数によりシグナル・ハンドラを登録します。シグナルは引数で指定された値で、シグナル・ハンドラはSignalHandler関数です。
44行目
受信したシグナルがSIGQUITかをチェックします。SIGQUITの場合はexit関数で終了します。
62行目
処理を続行する場合は再度シグナル・ハンドラを有効にしなければならないため、SetSignal関数によりシグナル・ハンドラを再登録します。