Сигнал SIGFPE вместо NaN

98
30 августа 2021, 10:20

В некоторых linux системах вместо исключения в операции с плавающей точкой (сигнал SIGFPE) появляется значение NaN и вероятность отыскать ошибку близка к нулю и о существовании ошибки можно лишь догадываться, когда в конце появилось NaN. Хотелось бы, чтобы такое проблемное место убивало программу (тогда отладчик может помочь отыскать проблемное место). Собственно говоря, суть вопроса в том, как сделать, что бы ошибка в операции с плавающей точкой приводила к SIGFPE.

Answer 1

В реализации libc от GNU есть нестандартная функция feenableexcept()¹. Она же есть и во многих других libc (в том числе {Free,Open}BSD и uclibc). В качестве аргумента она принимает битовую маску исключительных ситуаций (аналогично fesetexceptflag() и др.):

  • FE_DIVBYZERO — деление на ноль
  • FE_OVERFLOW — переполнение
  • FE_UNDERFLOW — обратное переполнение
  • FE_INEXACT — неточный результат
  • FE_INVALID — неверная операция

Все эти макросы определены только если платформа их поддерживает. Также определён макрос FE_ALL_EXCEPT, который представляет комбинацию всех вышеописанных.

Также для отмены сигнала есть fedisableexcept().

Как и многие другие расширения GNU, они требует определения features-test макроса _GNU_SOURCE до включения каких-либо системных хедеров. К сожалению, в Linux man-pages обе эти функции слабо документированы: о ней упомянуто только вскользь в man fenv. , так что за какими-то подробностями лучше обратиться к исходникам и/или к справке из других ОС.

К сожалению, после получения SIGFPE восстановить нормальную работу непосредственно с точки возникновения сигнала невозможно, так что останется только завершиться (или сделать longjmp(), см. комментарии). Так что, пожалуй, данные функции ограничены к использованию только отладкой. Возврат из обработчика сигнала приведёт к зацикливанию т.к. выполнение продолжится с той же инструкции, которая вызвала ошибку.

Как это работает

Эти функции работают без какой-либо магии со стороны компилятора или системы. Всё завязано исключительно на возможности процессора. На x86 просто устанавливаются соответствующие биты в управляющем регистре CW для x87 и, при возможности, в MXCSR для SSE (см. FLDCW и LDMXCSR соответственно). Далее при определении соответствующей ситуации процессор просто выкинет исключение, которое обработает ядро и отправит сигнал текущему процессу.

Пример

Подсказка: дабы получить разные ошибки, нужно раскомментировать строки в конце:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fenv.h>
#include <unistd.h>
#include <signal.h>
#define BUF_SZ 1024
void sigfpe_hdl(int signum, siginfo_t *si, void *ctx) {
  if (signum == SIGFPE) {
    char buf[BUF_SZ];
    char *msg;
    switch (si->si_code) { /* see `man 2 sigaction` for the list */
      case FPE_INTDIV: msg = "Integer divide by zero"          ; break;
      case FPE_INTOVF: msg = "Integer overflow"                ; break;
      case FPE_FLTDIV: msg = "Floating-point divide by zero"   ; break;
      case FPE_FLTOVF: msg = "Floating-point overflow"         ; break;
      case FPE_FLTUND: msg = "Floating-point underflow"        ; break;
      case FPE_FLTRES: msg = "Floating-point inexact result"   ; break;
      case FPE_FLTINV: msg = "Floating-point invalid operation"; break;
      case FPE_FLTSUB: msg = "Subscript out of range"          ; break;
      default: msg = "Unknown FPE";
    }
    char *msgend=buf;
    msgend = stpncpy (msgend,"Received SIGFPE. Reason: ", BUF_SZ - (msgend-buf));
    msgend = stpncpy (msgend, msg,                        BUF_SZ - (msgend-buf));
    msgend = stpncpy (msgend, ".\n",                      BUF_SZ - (msgend-buf));
    write (STDERR_FILENO, buf, msgend - buf);
  }
  exit (EXIT_FAILURE);
}
int main() {
  struct sigaction sa_fpe = {
    .sa_sigaction = sigfpe_hdl,
    .sa_flags = SA_SIGINFO,
  };
  sigaction (SIGFPE, &sa_fpe, 0);
  
  feenableexcept (FE_ALL_EXCEPT);
  
  int i,j;
  
//  i=10; j=0; i=i/j;  // integer divide by zero
   float x, y;
//   x = 10.0;  y= 0.0;   x = x/y;  // divide by zero
//   x = 1e+25; y= 1e+25; x = x*y;  // overflow          
//   x = 1e-25; y= 1e-25; x = x*y;  // underflow
//   x = 0.2;   y= 0.1;   x = x+y;  // inexact result
//   x = 0.0;   y= 0.0;   x = x/y;  // invalid operation
}

¹ Аналогичного результата можно добиться с помощью стандартных fegetenv()+fesetexceptflag()+fsetenv().

READ ALSO
Помогите ускорить код

Помогите ускорить код

есть кодон принимает число и раскладывает на множители

161
Stack overflow в рекурсивной функции

Stack overflow в рекурсивной функции

По ходу выполнения программы возникает исключение "Необработанное исключение по адресу 0x79E917D7 (ucrtbaseddll) в ConsoleApplication7

92
Возникли проблемы при вычислении суммы бесконечного ряда

Возникли проблемы при вычислении суммы бесконечного ряда

Написал код, согласно требованиям (задание приложу в скриншоте)Все вычисляет,ошибок не выдает, однако значение проверки суммы ряда отличается...

117
Как добавить программу в автозагрузку используя WinAPI (C/C++)

Как добавить программу в автозагрузку используя WinAPI (C/C++)

Какая функция, описание которой находится в заголовочном файле windowsh (насколько я знаю, она там), отвечает за автозапуск и как прописать, заранее...

128