Oracle® Solaris Studio 12.4:数值计算指南

退出打印视图

更新时间: 2015 年 1 月
 
 

4.4.3 使用 libm 异常处理扩展来查找异常

使用 libm 中 C99 浮点环境函数的异常处理扩展,C/C++ 程序可通过多种方法来查找异常。这些扩展包括可建立处理程序并同时启用陷阱(正如 ieee_handler 所执行的操作那样)的函数,但是它们提供更大的灵活性。它们还支持将有关浮点异常的回顾诊断消息记录到选定文件中。

4.4.3.1 fex_set_handling(3m)

fex_set_handling 函数允许您选择某个选项或模式来处理每种类型的浮点异常。fex_set_handling 的调用语法是:

ret = fex_set_handling(ex, mode, handler);

ex 参数指定要应用调用的异常集合。它必须是列在Table 4–4 的第一列中的值的按位“或”。(这些值在 fenv.h 中定义。)

表 4-4  fex_set_handling 的异常代码
异常
FEX_INEXACT
不精确结果
FEX_UNDERFLOW
下溢
FEX_OVERFLOW
溢出
FEX_DIVBYZERO
除以零
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(溢出、除以零和所有的无效运算)和 FEX_ALL(所有异常)。

mode 参数指定要为指出的异常建立的异常处理模式。有五种可能的模式:

  • 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 中的某个值)和一个指针(它所指向的结构用来记录有关导致该异常的运算的附加信息)组成。该结构的内容在下一节和 fex_set_handling(3m) 手册页中介绍。

请注意,如果指定的 modeFEX_NONSTOPFEX_NOHANDLERFEX_ABORThandler 参数会被忽略。如果指定的模式是为指定的异常建立的,fex_set_handling 会返回非零值,否则将返回零。在下面的示例中,返回值将被忽略。

下面的示例假设使用 fex_set_handling 方法来查找某些类型的异常。要中止 0/0 异常,请使用以下命令:

fex_set_handling(FEX_INV_ZDZ, FEX_ABORT, NULL);

要针对溢出和除以零安装 SIGFPE 处理程序,请使用以下命令:

fex_set_handling(FEX_OVERFLOW | FEX_DIVBYZERO, FEX_SIGNAL,
    handler);

在上面的示例中,处理程序函数可打印通过 sip 参数提供给 SIGFPE 处理程序的诊断信息,如上面的子段所示。与之相反,下面的示例打印有关该异常的、提供给安装在 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 异常处理扩展查找异常的另一种方法是启用对有关浮点异常的回顾诊断消息的记录。当您启用对回顾诊断消息的记录时,系统会记录有关某些异常的信息。这些信息包括异常的类型、导致该异常的指令的地址、将要处理该异常的方式以及类似于调试器所生成的跟踪的栈跟踪。用回顾诊断消息记录的栈跟踪只包含指令地址和函数名;对于其他调试信息(如行号、源文件名和参数值),必须使用调试器。

并非每个所发生的异常都包含在回顾诊断日志中;如果出现了这种情况,则典型日志将非常大,而且将不可能隔离与众不同的异常。相反,日志记录机制会消除冗余的消息。在以下任一情况下,消息会被认为冗余:

  • 以前在同一位置(即,具有相同的指令地址和栈跟踪)记录了同一个异常,或者

  • FEX_NONSTOP 模式对于该异常有效,而且它的标志以前被引发过。

尤其是,在大多数程序中,对于每种类型的异常将只记录第一次出现的异常。当 FEX_NONSTOP 处理模式对于某个异常有效时,如果通过 C99 浮点环境的任一函数清除该异常的标志,则允许在该异常下次发生时记录它,但前提是它不在以前记录它的位置发生。

要启用日志记录,请使用 fex_set_log 函数指定应将消息传递到的文件。例如,要将消息记录到标准错误文件,请使用:

fex_set_log(stderr);

以下代码示例将以下两个功能组合在一起:对回顾诊断消息的记录与上一节中阐释的共享对象预装功能。通过创建下面的 C 源文件、将它编译为共享对象、通过在 LD_PRELOAD 环境变量中提供共享对象的路径名来预装共享对象、在 FTRAP 环境变量中指定一个或多个异常的名称(用逗号分开),可同时针对指定的异常中止该程序,并获取可显示每个异常发生位置的诊断输出结果。

示例 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) 手册页。