11    例外条件の処理

例外は,現在実行しているスレッドで起こる特別な状態であり,その状態を認識して適切なアクションをとる実行コードが必要です。 このコードは,例外ハンドラと呼ばれます。

終了ハンドラは,制御フローが特定のコード本体を出るときに実行されるコードから構成されます。 終了ハンドラは,コード本体から出ることにより,メモリ・バッファの解放やロックのリリースなどのタスクを実行して,設定されたコンテキストをクリーンアップする場合に有効です。

この章では,次の項目について説明します。

11.1    例外処理の概要

Tru64 UNIX システムでは,『Alpha Architecture Reference Manual』で説明しているように,ハードウェアが例外をトラップして,それをオペレーティング・システムのカーネルに引き渡します。 カーネルは,不良メモリ・アクセスや算術トラップなどのハードウェア例外を,シグナルに変換します。 プロセスは,シグナルの引き渡しを可能にし,シグナル・ハンドラを設定して,プロセス内でシグナルを処理します。

『Calling Standard for Alpha Systems』では,Tru64 UNIX システムにおける例外的なイベントの処理を可能にする特別な構造とメカニズムについて,詳しく定義しています。 この規格に定義されている処理には,次のようなものがあります。

Tru64 UNIX の C コンパイラの構造化例外処理機能をサポートする実行時例外ディスパッチャは,規格に記述されているフレーム・ベースの例外ハンドラの 1 例です。 構造化例外処理の説明については,11.3 節を参照してください。

以降の各項で,『Calling Standard for Alpha Systems』に定義されている例外処理メカニズムをサポートする Tru64 UNIX の構成要素について簡単に説明します。

11.1.1    C コンパイラ構文

Tru64 UNIX の C コンパイラが提供する構文では,ユーザ定義およびシステム定義の例外条件に対して,コード領域を保護することができます。 このメカニズムは,構造化例外処理と呼ばれ,ユーザが例外ハンドラおよび終了ハンドラを定義するとともに,保護するコード領域を示すことができます。

c_excpt.h ヘッダ・ファイルで定義しているシンボルと関数を使用すると,ユーザ例外処理コードは,現在の例外コードおよび例外について説明しているその他の情報を取得できます。

11.1.2    libexc ライブラリ・ルーチン

例外サポート・ライブラリ /usr/ccs/lib/cmplrs/cc/libexc.a では,次の機能を持つルーチンを提供しています。

C 言語の構造化例外ハンドラは,最後の 2 つのカテゴリのルーチンを呼び出して,ユーザ・コードが例外を処理して実行を再開し,ユーザ定義の例外ハンドラを探索してディスパッチできるようにします。 この処理については,11.3 節で説明しています。 /usr/ccs/lib/cmplrs/cc/libexc.a で提供するルーチンについての詳細は,そのルーチンのリファレンス・ページを参照してください。

11.1.3    例外処理をサポートするヘッダ・ファイル

さまざまなヘッダ・ファイルで,例外処理システムおよびプロシージャ・コンテキストの操作をサポートする構造体を定義しています。 表 11-1 に,このようなヘッダ・ファイルの一覧を示します。

表 11-1:  例外処理をサポートするヘッダ・ファイル

ファイル 説明
excpt.h 例外コードの構造体および Tru64 UNIX の例外コードの数を定義する。 システム例外,コンテキスト・レコード,関連するフラグ,シンボリック定数,実行時プロシージャ・タイプ,および libexc.a で提供される関数用のプロトタイプも定義する。 詳細は excpt(4) を参照。
c_excpt.h C 言語の構造化例外ハンドラおよび終了ハンドラが使用するシンボルを定義する。 また,例外コードを返す例外情報構造体と関数,その他の例外情報,および終了ハンドラが呼び出される状態に関する情報も定義する。 詳細は c_excpt(4) を参照。
machine/fpu.h IEEE 浮動小数点例外の引き渡しとその出現を記録する情報を探索するルーチン,ieee_set_fp_control および ieee_get_fp_control 用のプロトタイプを定義する。 また,これらのルーチンをサポートする構造体と定数も定義する。 詳細は ieee(3) を参照。
pdsc.h 実行時プロシージャ記述子およびコード範囲記述子などの構造体を定義する。 これらは,『Calling Standard for Alpha Systems』に記述されているプロシージャ・タイプおよびフロー制御メカニズムに対して,実行時コンテキストを提供する。 詳細は pdsc(4) を参照。

11.2    ユーザ・プログラムで起こす例外

ユーザ・プログラムは,通常,次の 2 つの方法で例外を起こします。

11.3 節の例は,例外を明示的に起こして,シグナルを例外に変換する方法を示しています。

11.3    構造化例外ハンドラの作成

Tru64 UNIX の C コンパイラが提供する構造化例外処理機能を使用すると,特定のコード・シーケンスにおいて特定の例外条件が発生したときの処理を記述できます。 これらの機能は常に有効です (cc コマンドの -ms オプションは不要です)。 構造化例外ハンドラを設定する構文は,次のとおりです。

try {     try-body    } except ( exception-filter) {      exception-handler   }

try-body は,例外ハンドラが保護する文または文のブロックです。 try 本体を実行中に例外が起きた場合,C 固有の実行時ハンドラは exception-filter を評価して,制御を関連する exception-handler に移すか,外部レベルの try 本体でハンドラの探索を継続するか,あるいは,例外が起きた場所から通常の実行を継続するかを決定します。

exception-filter は,try 本体を保護する例外ハンドラに関連する式です。 これは,単純式でも,式を評価する関数を呼び出しても構いません。 例外フィルタは,例外ディスパッチャが例外処理を終了するために,次のいずれかの整数値に評価されなければなりません。

例外フィルタにおいて,次の 2 つの intrinsic 関数が,フィルタされている例外に関する情報にアクセスできます。

long                          exception_code ();  Exception_info_ptr     exception_info ();

exception_code 関数は,例外コードを返します。 exception_info 関数は,EXCEPTION_POINTERS 構造体へのポインタを返します。 このポインタを使用すると,例外が起きたときのマシン状態 (たとえば,システム例外やコンテキスト・レコード) にアクセスできます。 詳細は, excpt(4) および c_excpt(4) を参照してください。

exception_code 関数は,例外フィルタまたは例外ハンドラで使用できます。 しかし,exception_info 関数は,例外フィルタ内でのみ使用可能です。 例外ハンドラにおいて,exception_info 関数から返された情報を使用する必要がある場合は,その関数をフィルタ内で呼び出して,情報をローカルに格納しなければなりません。 フィルタ外で例外構造体を参照する必要がある場合は,同時にそれらをコピーしておかなければなりません。 これは,それらのストレージが,フィルタの実行中のみ有効なためです。

例外が起こると,例外ディスパッチャは,ハンドラが設定されているフレームに到達するまで,仮想的に実行時スタックを展開します。 ディスパッチャは最初に,例外が起きたときに現在のスタック・フレームであったスタック・フレームで例外ハンドラを探索します。

ハンドラがこのスタック・フレームにない場合には,ディスパッチャは,現在のスタック・フレームおよび介在するスタック・フレームをそのままにして,例外ハンドラを設定しているフレームに到達するまで,仮想的にスタックを (それ自身のコンテキストにおいて) 展開します。 その後,そのハンドラに関連する例外フィルタを実行します。

この例外ディスパッチのフェーズでは,ディスパッチャは実行時スタックを仮想的にのみ展開することに注意してください。 つまり,スタック上に存在している呼び出しフレームは依然その場所にあります。 例外ハンドラを見つけられない場合,またはすべてのハンドラで例外が再度起こる場合は,例外ディスパッチャはシステムのラスト・チャンス・ハンドラを呼び出します。 ラスト・チャンス・ハンドラの設定方法については, exc_set_last_chance_handler(3) を参照してください。

例外フィルタを Pascal スタイルのネストしたプロシージャのように処理することによって,例外処理コードはフィルタ式を try...except ブロックを含むプロシージャの有効範囲内で評価します。 これにより,そのフィルタを含むプロシージャのスタック・フレームまで,スタックが実際に展開されていなくても,フィルタ式は,そのフィルタを含むプロシージャのローカル変数にアクセスできます。

例外ハンドラを実行する前に (たとえば,例外フィルタが EXCEPTION_EXECUTE_HANDLER を返す場合),例外ディスパッチャは実行時スタックを実際に展開して,制御を例外ハンドラに移した結果として終了した try...finally ブロックに対して設定された終了ハンドラを実行します。 ディスパッチャが例外ハンドラを呼び出すのは,その後だけです。

exception-handler は,例外条件を処理する複合文です。 これは,try...except 構文を含むプロシージャの有効範囲内で実行され,そのローカル変数にアクセスできます。 ハンドラは,例外の種類によって,さまざまな方法で例外に対応できます。 たとえば,エラーのログを取ったり,例外が生じる状況を修正することができます。

例外フィルタまたは例外ハンドラのどちらも,取得した例外情報を修正したり拡張し,C 言語の例外ディスパッチャに依頼して,外部の try 本体または以前の呼び出しフレームで設定された例外コードに新しい情報を引き渡すことができます。 この処理は,例外フィルタ内で行う方が簡単であり,最後に実行されているプロシージャのフレームで行なわれ,例外コンテキストは実行時スタックにそのまま残ります。 フィルタは,ディスパッチャに 0 を返すだけで処理を完了し,そのディスパッチャに次のハンドラの探索を継続することを要求します。

例外ハンドラが以前に設定されたハンドラを呼び出すためには,例外ハンドラは自分のコンテキストから,以前に設定されたハンドラで処理する別の例外を起こさなければなりません。

例 11-1 は,簡単な例外ハンドラを設定し,exc_raise_signal_exception シグナル・ハンドラによって,例外に変換されたセグメンテーション違反シグナル (SIGSEGV) を処理する方法を示しています。

例 11-1:  構造化例外としての SIGSEGV シグナルの処理

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <excpt.h>
 
void main(void)
{
    struct sigaction    act, oldact;
    char                *x=0;
 
/*
 * Set up things so that SIGSEGV signals are delivered. Set
 * exc_raise_signal_exception as the SIGSEGV signal handler
 * in sigaction.
 */
    act.sa_handler = exc_raise_signal_exception;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (sigaction(SIGSEGV, &act, &oldact) < 0)
        perror("sigaction:");
/*
 * If a segmentation violation occurs within the following try
 * block, the run-time exception dispatcher calls the exception
 * filter associated with the except statement to determine
 * whether to call the exception handler to handle the SIGSEGV
 * signal exception.
 */
    try {
        *x=55;
    }
    /*
     * The exception filter tests the exception code against
     * SIGSEGV. If it tests true, the filter returns 1 to the
     * dispatcher, which then executes the handler; if it tests
     * false, the filter returns -1 to the dispatcher, which
     * continues its search for a handler in the previous run-time
     * stack frames. Eventually the last-chance handler executes.
     * Note: Normally the printf in the filter would be replaced
     * with a call to a routine that logged the unexpected signal.
     */
    except(exception_code() == EXC_VALUE(EXC_SIGNAL,SIGSEGV) ? 1 :
           (printf("unexpected signal exception code 0x%lx\n",
                   exception_code()), 0))
        {
            printf("segmentation violation reported: handler\n");
            exit(0);
        }
    printf("okay\n");
    exit(1);
}
 
 

次は,このプログラムの実行例です。

% cc -std0 segfault_ex.c -lexc
% a.out
segmentation violation reported in handler
 

例 11-2例 11-1 と同様に,シグナル例外の処理方法を示していますが,この場合は SIGFPE を処理します。 この例では,さらに,IEEE 浮動小数点例外であるゼロによる浮動除算を,ieee_set_fp_control() への呼び出しにより使用可能にする方法,およびハンドラがシステム例外レコードを読み取ることにより詳細な情報を取得する方法を示します。

例 11-2:  構造化例外としての IEEE 浮動小数点 SIGFPE の処理

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <excpt.h>
#include <machine/fpu.h>
#include <errno.h>
 
int main(void)
{
    Exception_info_ptr  except_info;
    system_exrec_type   exception_record;
    long                code;
    struct sigaction    act, oldact;
    unsigned long       float_traps=IEEE_TRAP_ENABLE_DZE;
    double              temperature=75.2, divisor=0.0, quot, return_val;
 
/*
 * Set up things so that IEEE DZO traps are reported and that
 * SIGFPE signals are delivered.  Set exc_raise_signal_exception
 * as the SIGFPE signal handler.
 */
    act.sa_handler = exc_raise_signal_exception;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (sigaction(SIGFPE, &act, &oldact) < 0)
        perror("sigaction:");
    ieee_set_fp_control(float_traps);
 
/*
 * If a floating divide-by-zero FPE occurs within the following
 * try block, the run-time exception dispatcher calls the
 * exception filter associated with the except statement to
 * determine whether the SIGFPE signal exception is to be
 * handled by the exception handler.
 */
 
    try {
        printf("quot = IEEE %.2f / %.2f\n",temperature,divisor);
        quot = temperature / divisor;
    }
/*
 * The exception filter saves the exception code and tests it
 * against SIGFPE. If it tests true, the filter obtains the
 * exception information, copies the exception record structure,
 * and returns 1 to the dispatcher which then executes the handler.
 * If the filter's test of the code is false, the filter
 * returns 0 to the handler, which continues its search for a
 * handler in previous run-time frames. Eventually the last-chance
 * handler executes. Note: Normally the filter printf is replaced
 * with a call to a routine that logged the unexpected signal.
 */
    except((code=exception_code()) == EXC_VALUE(EXC_SIGNAL,SIGFPE) ?
           (except_info = exception_info(),
            exception_record = *(except_info->ExceptionRecord), 1) :
           (printf("unexpected signal exception code 0x%lx\n",
                   exception_code()), 0))
/*
 * The exception handler follows and prints out the signal code,
 * which has the following format:
 *
 * 0x        8           0ffe               0003
 * |         |            |                  |
 * hex       SIGFPE      EXC_OSF facility   EXC_SIGNAL
 */
        { printf("Arithmetic error\n");
        printf("exception_code() returns 0x%lx\n", code);
        printf("EXC_VALUE macro in excpt.h generates 0x%lx\n",
               EXC_VALUE(EXC_SIGNAL, SIGFPE));
        printf("Signal code in the exception record is 0x%lx\n",
               exception_record.ExceptionCode);
/*
 * To find out what type of SIGFPE this is, look at the first
 * optional parameter in the exception record. Verify that it is
 * FPE_FLTDIV_TRAP).
 */
        printf("No. of parameters is %lu\n",
               exception_record.NumberParameters);
        printf("SIGFPE type is 0x%lx\n",
               exception_record.ExceptionInformation[0]);
/*
 * Set return value to IEEE_PLUS_INFINITY and return.
 */
        if (exception_record.ExceptionInformation[0] ==
            FPE_FLTDIV_TRAP)
            {
                *((long*)&return_val) = IEEE_PLUS_INFINITY;
                printf("Returning 0x%f to caller\n", return_val);
                return 0;
            }
/*
 * If this is a different kind of SIGFPE, return gracelessly.
 */
        else
            return -1;
        }
/*
 * We get here only if no exception occurred in the try block.
 */
    printf("okay: %f\n", quot);
    exit(1);
}
 
 

次は,このプログラムの実行例です。

% cc -std0 sigfpe_ex.c -lexc
% a.out
quot  = IEEE 75.20 / 0.00
Arithmetic error
exception_code() returns 0x80ffe0003
The EXC_VALUE macro in excpt.h generates 0x80ffe0003
The signal code in the exception record is 0x80ffe0003
No. of parameters is 1
SIGFPE type is 0x4
Returning 0xINF to caller
 

プロシージャ (または相互に関係のあるプロシージャのグループ) は,try...except 構造をいくつでも含むことができ,また,これらの構造はネストしても構いません。 try...except ブロック内で例外が起こると,システムはそのブロックに関連する例外ハンドラを呼び出します。

例 11-3 は,2 つのプライベートな例外コードによって定義され,最も内側の try ブロック内でこれら 2 つの例外のどちらかを起こす,複数の try...except ブロックの動作を示しています。

例 11-3:  複数の構造化例外ハンドラ

#include <excpt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define EXC_NOTWIDGET EXC_VALUE(EXC_C_USER, 1)
#define EXC_NOTDECWIDGET EXC_VALUE(EXC_C_USER, 2)
 
void getwidgetbyname(char *);
 
/*
 * main() sets up an exception handler to field the EXC_NOTWIDGET
 * exception and then calls getwidgetbyname().
 */
void main(int argc, char *argv[])
{
    char widget[20];
    long code;
    try {
        if (argc > 1)
            strcpy(widget, argv[1]);
        else
            {
                printf("Enter widget name: ");
                gets(widget);
            }
        getwidgetbyname(widget);
    }
    except((code=exception_code()) == EXC_NOTWIDGET)
        {
            printf("Exception 0x%lx: %s is not a widget\n",
                   code, widget);
            exit(0);
        }
}
/*
 * getwidgetbyname() sets up an exception handler to field the
 * EXC_NOTDECWIDGET exception. Depending upon the data it is
 * passed, its try body calls exc_raise_status_exception() to
 * generate either of the user-defined exceptions.
 */
void
getwidgetbyname(char* widgetname)
{
    long  code;
    try {
        if (strcmp(widgetname, "foo") == 0)
            exc_raise_status_exception(EXC_NOTDECWIDGET);
        if (strcmp(widgetname, "bar") == 0)
            exc_raise_status_exception(EXC_NOTWIDGET);
    }
/*
 * The exception filter tests the exception code against
 * EXC_NOTDECWIDGET. If it tests true, the filter returns 1
 * to the dispatcher; if it tests false, the filter returns -1
 * to the dispatcher, which continues its search for a handler
 * in the previous run-time stack frames. When the generated
 * exception is EXC_NOTWIDGET, the dispatcher finds its handler
 * in main()'s frame.
 */
    except((code=exception_code()) == EXC_NOTDECWIDGET)
        {
            printf("Exception 0x%lx: %s is not "
                   "a Hewlett-Packard-supplied widget\n",
                   code, widgetname);
            exit(0);
        }
    printf("widget name okay\n");
}
 
 

次は,このプログラムの実行例です。

% cc raise_ex.c -lexc
% a.out
Enter widget name: foo
Exception 0x20ffe009: foo is not a Hewlett-Packard-supplied widget
% a.out
Enter widget name: bar
Exception 0x10ffe009: bar is not a widget
 

11.4    終了ハンドラの作成

cc コンパイラは,保護されたコード本体から制御が渡されると,指定された終了コードのブロックが必ず実行されるようにします。 終了コードは,制御フローが保護されたコードを出る方法にかかわらず実行されます。 たとえば,保護されたコード本体の実行中に,例外またはその他のエラーが生じても,終了ハンドラは,クリーンアップ・タスクが確実に実行されるようにします。

終了ハンドラの構文は,次のとおりです。

try {     try-body    } finally {       termination-handler   }

try-body は,複合文として表現されたコードであり,終了ハンドラが保護します。 try 本体は,文のブロックまたはネストしたブロックの集まりでも構いません。 これには,次の文を含むことができます。 この文は,ブロックから直ちに出て,終了ハンドラを実行します。

leave;

注意

Tru64 UNIX の longjmp() ルーチンは,展開操作を使用しません。 したがって,フレーム・ベースの例外処理がある場合には,try-body または termination-handler から longjmp() を使用しないでください。 代わりに,展開操作を介してインプリメントされる exc_longjmp() を使用してください。

termination-handler は,try 本体が正常終了したか異常終了したかにかかわらず,制御フローが保護された try 本体を出ると実行される複合文です。 ブロック内の最後の文が実行された (つまり,本体の "}" に到達した) とき,保護された本体は正常終了したとみなされます。 leave 文を使用しても,正常終了します。 制御フローがその他の方法で保護された本体を出ると,その本体は異常終了します。 たとえば,例外や,returngotobreakcontinue などの制御文で保護された本体を出た場合です。

終了ハンドラは次の intrinsic 関数を呼び出して,保護された本体が正常終了したか異常終了したかを判断できます。

int abnormal_termination ();

try 本体がシーケンシャルに ("}" に到達することによって) 完了した場合,abnormal_termination 関数は 0 を返し,そうでない場合は 1 を返します。

終了ハンドラ自体は,シーケンシャルに終了することも,ハンドラの外に制御を渡して終了することもできます。 シーケンシャルに ("}" に到達することによって) 終了する場合,その後の制御フローは,次のように try 本体の終了方法に依存します。

例外フィルタと同様に,終了ハンドラは Pascal スタイルのネストしたプロシージャとして処理され,実行時スタックからフレームを削除せずに実行されます。 終了ハンドラは,プロシージャで宣言されているローカル変数に,このようにしてアクセスできます。

異常終了 (および例外) は,ほとんどのプログラムにとって,通常の制御フロー外と考えられるため,異常終了の処理には実行コストがかかります。 try 本体外への明示的な飛び越しは,異常終了とみなされることを覚えておいてください。 正常終了は単純な場合であり,実行時にかかるコストが少なくてすみます。

場合によっては,try 本体外への飛び越しを leave 文 (制御を最も内側の try 本体の終わりに移す) に置き換えて,try...finally ブロックを完了した後に状態変数を検査すると,このコストを回避できます。

終了ハンドラ自体は,制御の引き渡し (たとえば,gotobreakcontinuereturnexc_longjmp,または例外の発生) によって,シーケンシャルでない方法で終了する (たとえば,展開の異常終了) ことがあります。 この制御の引き渡しが別の try...finally ブロックに存在する場合,終了ハンドラが実行されます。

例 11-4 は,例外によって最も内側の try 本体が終了するとき,終了ハンドラおよび例外ハンドラが実行される順序を示しています。

例 11-4:  例外による try 本体の異常終了

#include <stdio.h>
#include <signal.h>
#include <excpt.h>
#include <errno.h>
 
#define EXC_FOO EXC_VALUE(EXC_C_USER, 1)
 
signed int
foo_except_filter(void)
{
    printf("2. The exception causes the exception filter "
           "to be evaluated.\n");
    return 1;
}
 
void main (void)
{
    try {
        try {
            printf("1. The main body executes.\n");
            exc_raise_status_exception(EXC_FOO);
        }
        finally {
            printf("3. The termination handler executes "
                   "because control will leave the "
                   "try...finally block to \n");
        }
    }
    except(foo_except_filter()) {
        printf("4. execute the exception handler.\n");
    }
}
 
 

次に示すのは,このプログラムの実行例です。

% cc segfault_ex2.c -lexc
% a.out
1. The main body executes.
2. The exception causes the exception filter to be evaluated.
3. The termination handler executes because control will leave the
   try...finally block to
4. execute the exception handler.