2

我正在努力尝试以便携式方式设置 std::fenv 。

基于这个cppreference 页面,似乎fesetexceptflag(const std::fexcept_t*,int)应该可以帮助我解决问题。另一方面,我发现 GNU 也提供了这个feenableexcept(int)功能。我的理解是feenablexceptGNU 特定的,虽然我很可能总是可以访问 GNU 的东西,但我希望只使用 std 的东西,也就是说,坚持使用fesetexceptflag. 我写了一个小测试,发现该feenableexcept方法有效,而该方法fesetexceptflag无效。这是两个例子。交换 main 开头的两行注释,得到版本 1 ( fesetexceptflag) 和版本 2 ( feenableexcept):

#include <cfenv>
#include <csignal>
#include <cstdio>

void signal_handler (int signum) {
  printf ("signal %d caught.\n",signum);
  exit(1);
}

int main(int,char**){
  std::fexcept_t my_flag = FE_DIVBYZERO;
  fesetexceptflag(&my_flag,FE_ALL_EXCEPT); // Uncomment this for version 1
  // feenableexcept(my_flag); // Uncomment this for version 2

  int mask = fetestexcept(FE_ALL_EXCEPT)
  printf("current mask: %d\n",mask);
  printf("mask is FE_DIVBYZERO: %s\n",mask==FE_DIVBYZERO ? "yes" : "no");
  signal(SIGFPE, signal_handler);

  double one  = 1.0;
  double zero = 0.0;
  double my_inf = one/zero;
  printf("All done!\n");
  return 0;
}

版本 1 输出:

current mask: 4
mask is FE_DIVBYZERO: yes
All done!

版本 2 输出:

current mask: 0
mask is FE_DIVBYZERO: no
signal 8 caught.

因此,似乎版本 1 在 fenv 中正确设置了异常标志,但未能引发 SIGFPE,而版本 2 未设置异常标志,但确实引发了 SIGFPE。这里发生了什么事?我是否误解了 的文档fesetexceptflag?我的理解是,它会抓取第一个 arg 中在第二个 arg 中处于活动状态的所有位,并将它们放入 fenv 中(这似乎正在发生)。但是,它似乎无效。另一方面,版本 2 具有 0 掩码 fenv,但成功提高了 SIGFPE。我很困惑。

我在 linux 机器(Red Hat)上使用 gcc 8.2.0,如果有帮助的话。

4

2 回答 2

1

我是否误解了 fesetexceptflag 的文档?

是的。fesetexceptflag表示:设置此异常标志以表示已报告异常。

的正确用法fetestexcept是:

feclearexcept(FE_ALL_EXCEPT);
int mask = fetestexcept(FE_ALL_EXCEPT);
printf("current mask: %d\n",mask);
printf("FE_DIVBYZERO before: %s\n",std::fetestexcept(FE_DIVBYZERO) ? "yes" : "no"); // no
double my_inf = one/zero;
int mask = fetestexcept(FE_ALL_EXCEPT);
printf("current mask: %d\n",mask);
printf("FE_DIVBYZERO after: %s\n",std::fetestexcept(FE_DIVBYZERO) ? "yes" : "no"); // yes

没有标准方法可以使浮点异常引发信号。这就是 glibc 为您提供的功能。您可以自己发出信号:

if (fetestexcept(FE_ALL_EXCEPT))
    raise(SIGFPE);
于 2020-03-18T00:13:38.210 回答
1

我是否误解了 fesetexceptflag 的文档?

是的。例如,当您这样做时,1.0/0FE_DIVBYZERO标志在当前环境中升起。fesetexceptflag不允许您决定除以 0 时fesetexceptflag发生什么。让我们检查已经执行的操作是否导致异常。

您只能使用math_errhandling宏查看浮点异常会发生什么。这是一个宏,它只告诉使用errno或者浮点异常。

这个小例子可以提供一些启示:

#include <cfenv>
#include <cstdio>
#pragma STDC FENV_ACCESS ON
#if math_errhandling != MATH_ERREXCEPT
   #error This code needs to use floating point exceptions
#endif

int main() {
    std::feclearexcept(FE_ALL_EXCEPT);
    printf("%s\n", std::fetestexcept(FE_DIVBYZERO) ? "FE_DIVBYZERO" : "no FE_DIVBYZERO");
    double a = 1.0/0;
    printf("%s\n", std::fetestexcept(FE_DIVBYZERO) ? "FE_DIVBYZERO" : "no FE_DIVBYZERO");
}

将输出:

no FE_DIVBYZERO
FE_DIVBYZERO

fexcept_t您可以恢复当前设置标志的实现定义表示。你不能做std::fexcept_t my_flag = FE_DIVBYZERO;- 好吧,你可以,但内容fexcept_t是实现定义的,所以结果将是实现定义的。不能fexcept_t手动修改。您只能使用 保存当前引发的浮点异常,进行一些可以抛出您想要检查的计算,然后使用相同的标志fegetexceptflag恢复浮点异常。到那时fesetexceptflag你修改fexcept_tsesetexceptflag然后用.feclearexceptferaiseexceptfegetexceptflag

抛出SIGFPE信号是扩展C99 J.5.17p1。它可能会附加或代替设置 errno 或浮点标志发生。扩展是用 gnu 函数激活的feenableexcept

于 2020-03-18T00:14:04.540 回答