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

印刷ビューの終了

更新: 2015 年 1 月
 
 

4.4.3 libm の例外処理拡張機能を使用して例外を特定する

C/C++ プログラムでは、libm にある C99 浮動小数点環境関数の例外処理拡張機能を使用し、いくつかの方法で例外を特定できます。これらの拡張機能には、ieee_handler による処理と同様に、ハンドラを設定して同時にトラップを有効にできる関数が含まれていますが、これらの関数は ieee_handler よりも柔軟です。これらの拡張機能は、選択されたファイルに対する、浮動小数点例外に関する遡及診断メッセージのロギングもサポートしています。

4.4.3.1 fex_set_handling(3m)

fex_set_handling 関数を使用すると、浮動小数点例外のそれぞれのタイプを処理するためのいつくかのオプション (またはモード) の 1 つを選択できます。fex_set_handling を呼び出すための構文は次のとおりです。

ret = fex_set_handling(ex, mode, handler);

引数 ex には、呼び出しを適用する一連の例外を指定します。この引数は、Table 4–4 の最初の列に示されている値のビット単位の「or」である必要があります(これらの値は、fenv.h に定義されています)。

表 4-4  fex_set_handling の例外コード
例外
FEX_INEXACT
不正確な結果
FEX_UNDERFLOW
アンダーフロー
FEX_OVERFLOW
オーバーフロー
FEX_DIVBYZERO
0 による除算
FEX_INV_ZDZ
0/0 の無効な演算
FEX_INV_IDI
無限大/無限大の無効な演算
FEX_INV_ISI
無限大-無限大の無効な演算
FEX_INV_ZMI
0*無限大の無効な演算
FEX_INV_SQRT
負数の平方根
FEX_INV_SNAN
シグナルを発生する NaN に対する演算
FEX_INV_INT
無効な整数変換
FEX_INV_CMP
無効な非順序付け比較

便宜上、fenv.h には、FEX_NONE (例外なし)、FEX_INVALID (すべての無効な演算例外)、FEX_COMMON (オーバーフロー、0 による除算、およびすべての無効な演算)、および FEX_ALL (すべての例外) の各値も定義されています。

引数 mode には、示された例外に設定する例外処理モードを指定します。指定可能なモードは 5 つあります。

  • FEX_NONSTOP モードでは、IEEE 754 のデフォルトの停止しない動作になります。これは、例外のトラップを無効にしておくことと同等です。ieee_handler と異なり、fex_set_handling では、無効な演算例外の特定のタイプに対してデフォルト以外の処理を設定し、残りのタイプは IEEE のデフォルト処理のままにできます。

  • FEX_NOHANDLER モードは、ハンドラを設定せずに例外のトラップを有効にすることと同じです。例外が発生すると、システムは、あらかじめインストールされている SIGFPE ハンドラが存在する場合はそのハンドラに制御を渡し、存在しない場合は処理を中止します。

  • FEX_ABORT モードでは、例外が発生するとプログラムは abort(3c) を呼び出します。

  • FEX_SIGNAL は、示された例外に対して、引数 handler によって指定された処理関数をインストールします。これらの例外のいずれかが発生すると、ieee_handler によってインストールされているかのように、同じ引数を使用してハンドラが呼び出されます。

  • FEX_CUSTOM は、示された例外に対し、handler によって指定された処理関数をインストールします。FEX_SIGNAL モードと異なり、例外が発生すると、簡略化された引数リストを使用してハンドラが呼び出されます。この引数は、整数 (値はTable 4–4 に示されている値の 1 つ) と、例外の原因となった演算に関する追加情報を記録する構造体を指すポインタから構成されます。この構造体の内容は、次のセクションおよび fex_set_handling(3m) のマニュアルページで説明されています。

指定された modeFEX_NONSTOPFEX_NOHANDLER、または FEX_ABORT である場合、handler パラメータは無視されます。fex_set_handling は、示された例外に対して指定されたモードが設定される場合はゼロ以外の値を返し、それ以外の場合はゼロを返します。次の例では、戻り値は無視されます。

次の例は、fex_set_handling を使用して特定のタイプの例外を見つける方法を示しています。0/0 例外で停止するようにするには、次を使用します。

fex_set_handling(FEX_INV_ZDZ, FEX_ABORT, NULL);

オーバーフローおよび 0 による除算に対する SIGFPE ハンドラをインストールするには、次を使用します。

fex_set_handling(FEX_OVERFLOW | FEX_DIVBYZERO, FEX_SIGNAL,
    handler);

前の例では、前のサブセクションで示したように、ハンドラ関数は SIGFPE ハンドラに対する sip パラメータを介して渡される診断情報を出力できました。一方、次の例では、FEX_CUSTOM モードでインストールされたハンドラに渡される例外に関する情報を出力します。詳細は、fex_set_handling(3m) のマニュアルページを参照してください。

使用例 4-2  FEX_CUSTOM モードでインストールされたハンドラに渡される情報の出力
#include <fenv.h>

void handler(int ex, fex_info_t *info)
{
    switch (ex) {
    case FEX_OVERFLOW:
        printf("Overflow in ");
        break;
    case FEX_DIVBYZERO:
        printf("Division by zero in ");
        break;

    default:
        printf("Invalid operation in ");
    }
    switch (info->op) {
    case fex_add:
        printf("floating point add\n");
        break;
    case fex_sub:
        printf("floating point subtract\n");
        break;
    case fex_mul:
        printf("floating point multiply\n");
        break;
    case fex_div:
        printf("floating point divide\n");
        break;
    case fex_sqrt:
        printf("floating point square root\n");
        break;
    case fex_cnvt:
        printf("floating point conversion\n");
        break;
    case fex_cmp:
        printf("floating point compare\n");
        break;
    default:
        printf("unknown operation\n");
    }
    switch (info->op1.type) {
    case fex_int:
        printf("operand 1: %d\n", info->op1.val.i);
        break;
    case fex_llong:
        printf("operand 1: %lld\n", info->op1.val.l);
        break;
    case fex_float:
        printf("operand 1: %g\n", info->op1.val.f);
        break;
    case fex_double:
        printf("operand 1: %g\n", info->op1.val.d);
        break;

    case fex_ldouble:
        printf("operand 1: %Lg\n", info->op1.val.q);
        break;
    }
    switch (info->op2.type) {
    case fex_int:
        printf("operand 2: %d\n", info->op2.val.i);
        break;
    case fex_llong:
        printf("operand 2: %lld\n", info->op2.val.l);
        break;
    case fex_float:
        printf("operand 2: %g\n", info->op2.val.f);
        break;
    case fex_double:
        printf("operand 2: %g\n", info->op2.val.d);
        break;
    case fex_ldouble:
        printf("operand 2: %Lg\n", info->op2.val.q);
        break;
    }
}
...
fex_set_handling(FEX_COMMON, FEX_CUSTOM, handler);

上記の例のハンドラは、発生した例外のタイプ、原因となった演算の種類、およびオペランドを報告します。このハンドラは、例外が発生した場所を示しません。例外が発生した場所を特定するには、遡及診断を使用できます。

4.4.3.2 遡及診断

libm の例外処理拡張機能を使用して例外を特定するもう 1 つの方法は、浮動小数点例外に関する遡及診断メッセージのロギングを有効にすることです。遡及診断のロギングを有効にすると、システムは特定の例外に関する情報を記録します。この情報には、例外のタイプ、その原因となった命令のアドレス、例外の処理方法、およびデバッガによって生成されるものに類似したスタックトレースが含まれます。遡及診断メッセージとともに記録されるスタックトレースには、命令のアドレスと関数名のみが含まれています。行番号、ソースファイル名、引数の値などのほかのデバッグ情報を調べるには、デバッガを使用する必要があります。

遡及診断のログには、発生した例外ごとの情報は含まれていません。例外ごとの情報を記録すると、一般にログが非常に大きくなり、特異な例外を特定することが不可能になります。代わりに、ロギングメカニズムは冗長なメッセージを取り除きます。次の 2 つの状況のいずれかである場合、メッセージは冗長と見なされます。

  • 同じ例外が同じ位置 (つまり、同じ命令アドレスとスタックトレース) で前に記録されている。

  • 例外に対して FEX_NONSTOP モードが有効になっていて、そのフラグが前に発生している。

具体的には、ほとんどのプログラムでは、それぞれのタイプの例外が最初に発生したときにのみログに記録されます。ある例外に対して FEX_NONSTOP 処理モードが有効な場合、任意の C99 浮動小数点環境関数を使用してそのフラグをクリアすると、その例外が次に発生したときにログに記録されます (前にログ記録された位置で発生していない場合)。

ロギングを有効にするには、fex_set_log 関数を使用してメッセージを転送するファイルを指定します。たとえば、メッセージを標準のエラーファイルに記録するには、次のように記述します。

fex_set_log(stderr);

次のコード例では、前のセクションで示した共有オブジェクトの事前ロード機能と遡及診断のロギングを組み合わせています。次の C ソースファイルを作成して、それを共有オブジェクトにコンパイルし、LD_PRELOAD 環境変数にそのパス名を指定することによってその共有オブジェクトを事前ロードして、FTRAP 環境変数に 1 つ以上の例外の名前をコンマで区切って指定すると、指定した例外の発生時にプログラムを中止すると同時に、各例外が発生した位置を示す遡及診断出力を取得できます。

使用例 4-3  遡及診断のロギングと共有オブジェクトの事前ロードの組み合わせ
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fenv.h>

static struct ftrap_string {
    const char  *name;
    int         value;
} ftrap_table[] = {
    { "inexact", FEX_INEXACT },
    { "division", FEX_DIVBYZERO },

    { "underflow", FEX_UNDERFLOW },
    { "overflow", FEX_OVERFLOW },
    { "invalid", FEX_INVALID },
    { NULL, 0 }
};

#pragma init (set_ftrap)
void set_ftrap()
{
    struct ftrap_string  *f;
    char                 *s, *s0;
    int                  ex = 0;

    if ((s = getenv("FTRAP")) == NULL)
        return;

    if ((s0 = strtok(s, ",")) == NULL)
        return;

    do {
        for (f = ftrap_table[0]; f->name != NULL; f++) {
            if (!strcmp(s0, f->name))
                ex |= f->value;
        }
    } while ((s0 = strtok(NULL, ",")) != NULL);

    fex_set_handling(ex, FEX_ABORT, NULL);
    fex_set_log(stderr);
}

このセクションの始めに示したプログラム例とともに上記のコードを使用すると、SPARC ベースのシステムでは、次のような結果が出力されます。

env FTRAP=invalid LD_PRELOAD=./init.so a.out
Floating point invalid operation (sqrt) at 0x7fa98398 __sqrt, abort
  0x7fa9839c  __sqrt
  0x00010880  sqrtm1
  0x000108ec  main
Abort

上記の出力は、ルーチン sqrtm1 内の平方根演算の結果として無効な演算例外が発生したことを示しています。

前述したように、x86 プラットフォームで共有オブジェクトの初期化ルーチンからトラップを有効にするには、標準の __fpstart ルーチンを無効にする必要があります。

典型的なログの出力を示した例については、Appendix A, 例を参照してください。一般的な情報については、fex_set_log(3m) のマニュアルページを参照してください。