Oracle® Solaris Studio 12.4: 数値計算ガイド

印刷ビューの終了

更新: 2015 年 1 月
 
 

4.4.2 シグナルハンドラを使用して例外を特定する

前のセクションでは、例外の最初の発生を特定するためにプログラムの始めにトラップを有効にする方法をいくつか紹介しました。これに対して、プログラム内でトラップを有効にすることによって例外の特定の発生を見つけることもできます。トラップを有効にしていても、SIGFPE ハンドラをインストールしていない場合は、トラップされた例外の次の発生時にプログラムが中止されます。SIGFPE ハンドラをインストールしてある場合は、トラップされた例外が次に発生すると、システムは制御をハンドラに渡し、ハンドラは例外が発生した命令のアドレスなどの診断情報を出力して、実行を中止するか、再開します。実行を再開して意味のある結果を得るには、次のセクションで説明するように、例外演算の結果をハンドラで設定する必要がある場合があります。

ieee_handler を使用すると、5 つの IEEE 浮動小数点例外のすべてのトラップを有効にすると同時に、指定した例外が発生したときにプログラムを中止するように設定するか、SIGFPE ハンドラを設定できます。SIGFPE ハンドラは、低レベルの関数 (sigfpe(3)、signal(3c)、または sigaction(2)) を使用してインストールすることもできますが、ieee_handler とは異なり、これらの関数ではトラップは有効になりません。浮動小数点例外は、そのトラップが有効である場合にのみ SIGFPE シグナルをトリガーできます。

4.4.2.1 ieee_handler (3m)

ieee_handler を呼び出すための構文は、次のとおりです。

i = ieee_handler(action, exception, handler)

2 つの入力パラメータ action および exception は文字列です。3 番目のパラメータ handlersigfpe_handler_type 型であり、floatingpoint.h に定義されています。

3 つの入力パラメータには次の値を指定できます。

入力パラメータ
C または C++ の型
指定できる値
action
char *
getsetclear
exception
char *
invaliddivisionoverflow
underflowinexact
allcommon
ハンドラ
sigfpe_handler_type
ユーザー定義のルーチン
SIGFPE_DEFAULT
SIGFPE_IGNORE
SIGFPE_ABORT

要求された action が "set" の場合、ieee_handler は exception に指定された例外について、handler に指定された処理関数を設定します。処理関数は、デフォルトの IEEE 動作を選択する SIGFPE_DEFAULT または SIGFPE_IGNORE、指定した例外が発生するとプログラムを中止させる SIGFPE_ABORT、指定した例外のいずれかが発生するとサブルーチン (SA_SIGINFO フラグを設定してインストールしたシグナルハンドラのパラメータ (sigaction(2) のマニュアルページを参照) が渡される) を起動するユーザーが用意したサブルーチンのアドレスのいずれかです。ハンドラが SIGFPE_DEFAULT または SIGFPE_IGNORE の場合、ieee_handler は指定した例外のトラップを無効にし、その他のハンドラの場合、ieee_handler はトラップを有効にします。

x86 プラットフォームでは、例外のトラップが有効にされて、対応するフラグが発生するたびに、浮動小数点ハードウェアがトラップします。そのため、誤ったトラップを防ぐには、ieee_handler を呼び出してトラップを有効にする前に、指定された exception ごとにプログラムでフラグをクリアする必要があります。

要求された action"clear" の場合、ieee_handler は指定した exception について現在インストールされている処理関数を取り消し、そのトラップを無効にします。これは、"set"SIGFPE_DEFAULT を指定した場合と同じです。action"clear"の場合、3 番目のパラメータは無視されます。

action が "set" および "clear" のいずれの場合も、ieee_handler は要求された操作を実行できる場合はゼロを返し、それ以外の場合はゼロ以外の値を返します。

要求された action"get" の場合、ieee_handler は指定した exception について現在インストールされているハンドラのアドレスを返します (ハンドラがインストールされていない場合は SIGFPE_DEFAULT を返します)。

次の例は、ieee_handler の使用方法を表すいくつかのコードの一部を示しています。この C のコードは、0 による除算が発生した場合にプログラムを中止します。

#include <sunmath.h> 
/* uncomment the following line on x86 systems */
    /* ieee_flags("clear", "exception", "division", NULL); */
    if (ieee_handler("set", "division", SIGFPE_ABORT) != 0)
        printf("ieee trapping not supported here \n");

Fortran の場合は次のコードのようになります。

#include <floatingpoint.h> 
c uncomment the following line on x86 systems
c     ieee_flags('clear', 'exception', 'division', %val(0))
      i = ieee_handler('set', 'division', SIGFPE_ABORT) 
      if(i.ne.0) print *,'ieee trapping not supported here'

次の C のコードは、すべての例外について IEEE のデフォルトの例外処理に戻します。

#include <sunmath.h> 
    if (ieee_handler("clear", "all", 0) != 0) 
        printf("could not clear exception handlers\n");

Fortran の場合は次のようになります。

      i = ieee_handler('clear', 'all', 0) 
      if (i.ne.0) print *, 'could not clear exception handlers'

4.4.2.2 シグナルハンドラからの例外の報告

ieee_handler によってインストールされた SIGFPE ハンドラが呼び出されると、オペレーティングシステムによって、発生した例外のタイプ、例外の原因である命令のアドレス、およびマシンの整数レジスタと浮動小数点レジスタの内容を示す追加の情報が渡されます。ハンドラはこの情報を検査して、例外と例外の発生場所を示すメッセージを出力します。

システムによって提供される情報にアクセスするには、ハンドラを次のように宣言します。この章の以降の部分では、C のコード例を示します。Fortran の SIGFPE ハンドラの例については、Appendix A, 例を参照してください。

#include <siginfo.h>
#include <ucontext.h>
 
void handler(int sig, siginfo_t *sip, ucontext_t *uap)
{
    ...
}

このハンドラを呼び出すと、送信されたシグナルの番号が sig パラメータに設定されます。シグナル番号は sys/signal.h で定義されています。SIGFPE のシグナル番号は 8 です。

sip パラメータは、シグナルに関する追加情報を記録する構造体を指しています。SIGFPE シグナルの場合、この構造体の関連メンバーは sip‐>si_code および sip->si_addr です (/usr/include/sys/siginfo.h を参照してください)。これらのメンバーの重要性は、システム、および SIGFPE シグナルがトリガーされたイベントによって異なります。

メンバー sip->si_code は、Table 4–3 に示されている SIGFPE シグナルのタイプのいずれかです。表示されているトークンは sys/machsig.h に定義されています。

表 4-3  算術例外のタイプ
SIGFPE のタイプ
IEEE のタイプ
FPE_INTDIV
n/a
FPE_INTOVF
n/a
FPE_FLTRES
不正確
FPE_FLTDIV
除算
FPE_FLTUND
アンダーフロー
FPE_FLTINV
無効
FPE_FLTOVF
オーバーフロー

上記の表に示されているように、各 IEEE 浮動小数点例外のタイプには対応する SIGFPE シグナルタイプがあります。整数の 0 による除算 (FPE_INTDIV) および整数オーバーフロー (FPE_INTOVF) は SIGFPE のタイプにも含まれていますが、これらは IEEE 浮動小数点例外ではないため、ieee_handler でハンドラをインストールすることはできません。これらの SIGFPE タイプのハンドラは sigfpe(3) を使用すると設定できます。ただし、整数オーバーフローは、デフォルトではすべての SPARC プラットフォームおよび x86 プラットフォームで無視されます。特殊な命令によって FPE_INTOVF タイプの SIGFPE シグナルを発生させることができますが、Sun のコンパイラはこのような命令を生成しません。

IEEE 浮動小数点例外に対応する SIGFPE シグナルの場合、メンバー sip‐>si_code は発生した例外を示します。x86 ベースのシステムでは、実際には、フラグが発生したもっとも優先度の高いアンマスクされた例外が示されます。通常、これは最後に発生した例外と同じです。メンバー sip->si_addr は、SPARC ベースのシステムでは例外の原因である命令のアドレスを保持し、x86 ベースのシステムではトラップされた時点の命令 (通常は例外の原因である命令の次の浮動小数点命令) のアドレスを保持します。

最後に、uap パラメータはトラップされた時点のシステムの状態を記録する構造体を指します。この構造体の内容はシステムによって異なります。いくつかのメンバーの定義については、/usr/include/sys/siginfo.h を参照してください。

オペレーティングシステムから提供される情報を使用すると、発生した例外のタイプと例外の原因である命令のアドレスを報告する SIGFPE ハンドラを記述できます。Example 4–1 は、そのようなハンドラを示しています。

使用例 4-1  SIGFPE ハンドラ
#include <stdio.h>
#include <sys/ieeefp.h>
#include <sunmath.h>
#include <siginfo.h>
#include <ucontext.h>

void handler(int sig, siginfo_t *sip, ucontext_t *uap)
{
    unsigned    code, addr;

    code = sip->si_code;
    addr = (unsigned) sip->si_addr;
    fprintf(stderr, "fp exception %x at address %x\n", code,
        addr);
}

int main()
{
    double  x;

    /* trap on common floating point exceptions */
    if (ieee_handler("set", "common", handler) != 0)
        printf("Did not set exception handler\n");
    /* cause an underflow exception (will not be reported) */
    x = min_normal();
    printf("min_normal = %g\n", x);
    x = x / 13.0;
    printf("min_normal / 13.0 = %g\n", x);

    /* cause an overflow exception (will be reported) */
    x = max_normal();
    printf("max_normal = %g\n", x);
    x = x * x;
    printf("max_normal * max_normal = %g\n", x);
    ieee_retrospective(stderr);
    return 0;
}

SPARC システムでは、このプログラムからの出力は次のようになります。

min_normal = 2.22507e-308
min_normal / 13.0 = 1.7116e-309
max_normal = 1.79769e+308
fp exception 4 at address 10d0c
max_normal * max_normal = 1.79769e+308
Note: IEEE floating-point exception flags raised:
    Inexact;  Underflow; 
IEEE floating-point exception traps enabled:
    overflow; division by zero; invalid operation; 
See the Numerical Computation Guide, ieee_flags(3M), ieee_handler(3M)

x86 プラットフォームでは、オペレーティングシステムが累積例外フラグのコピーを保存し、SIGFPE ハンドラを呼び出す前にそれらをクリアします。ハンドラによって保存されないかぎり、累積フラグはハンドラから戻ったときに失われます。このため、–xarch=386 を指定してコンパイルされていると、上記のプログラムからの出力にはアンダーフロー例外が発生したことが示されません。

min_normal = 2.22507e-308
min_normal / 13.0 = 1.7116e-309
max_normal = 1.79769e+308
fp exception 4 at address 8048fe6
max_normal * max_normal = 1.79769e+308
 Note: IEEE floating-point exception traps enabled: 
    overflow;  division by zero;  invalid operation; 
 See the Numerical Computation Guide, ieee_handler(3M)

ただし、デフォルトの場合、または –xarch=sse2 を指定してコンパイルされている場合は、PC がループ命令を抜けられないため、tjos テストプログラムがループします。Oracle Solaris Studio 12.4 の場合は、PC をインクリメントするコード行を追加すれば十分です。

uap -> UC_mcontext.gregs[REG_PC= +=5;

上記のコードは、–xarch=sse2 が指定されていて、SSE2 命令の長さが 5 バイトの場合にのみ使用できます。完全に汎用的な SSE2 の解決方法にするには、最適化されたコードをデコードして、次の命令の始まりを見つけます。代わりに fex_set_handling を使用してください。

多くの場合、トラップが有効であれば、例外の原因である命令は IEEE のデフォルトの結果を生成しません。上記の出力では、max_normal * max_normal に報告されている値は、オーバーフローした演算のデフォルトの結果 (つまり、正確な符号付き無限大) ではありません。通常は、意味のある値で計算を続行するために、トラップされた例外の原因である演算の結果を、SIGFPE ハンドラが提供する必要があります。これを行う 1 つの方法については、例外の処理を参照してください。