Jump to content

Tratamento de exceções em ponto flutuante pode ser problemático...


fredericopissarra

Recommended Posts

 

Recentemente topei com uma pegunta interessante… O padrão ISO 9989:1999 (e o de 2001) incorporaram funções para lidar com o “ambiente” de ponto flutuante via protótipos de funções contidas no header fenv.h. Isso, literalmente, torna obsoleto o mecanismo que usava a função matherr() — isso é particularmente verdadeiro para a glibc 2.27 em diante.

No mecanismo antigo, se a função matherr() estiver definida em seu programa, todas as exceções ocorridas em ponto flutuante são repassadas para ela, como se fosse um tratador de sinal. Isso não funciona mais assim. Agora, temos que usar fenv.h e suas funções… Eis um exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* Define _GNU_SOURCE para podermos usar
   as extensões feenableexcept() e fedisableexcept() */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fenv.h>
 
/* declara o tratador de SIGFPE */
static void myFPEhandler(int);
 
int main(void)
{
  double a, b, c;
 
  // Lê os valores de stdin.
  scanf( "%lg %lg", &a, &b );
 
  signal( SIGFPE, myFPEhandler );
 
  // Zera os flags indicadores de exceção.
  feclearexcept( FE_ALL_EXCEPT );
 
  // Tira a máscara da exceção de divisão por zero.
  feenableexcept( FE_DIVBYZERO );
 
  c = a / b;
 
  printf( "%g / %g = %g\n", a, b, c );
}
 
void myFPEhandler(int sig)
{
  fputs( "Math exception!\n", stderr );
  exit( sig + 128 );
}

Se, depois de compilarmos, tentarmos fazer:

 

E o troço parece funcionar perfeitamente! Mas, temos um problema: Não é possível testar qual exceção ocorreu!

No exemplo acima, retiramos a máscara da exceção de divisão por zero, mas poderíamos “desmascarar” todas as exceções, com feenableexcept(FE_ALL_EXCEPT);. Acontece que a interrupção tratada SIGFPE colocará todas as máscaras no lugar (“re-setadas” ou “setadas de novo”) e limpará os flags indicadores de exceção!!! Se tentarmos criar um tratador como:

1
2
3
4
5
6
7
8
9
10
void myFPEhandler(int sig)
{
  int except = fetestexcept( FE_ALL_EXCEPT );
  fprintf( stderr, "Exception %#x ", except );
  if ( except & FE_DIVBYZERO )
    fputs( "Division by zero!\n", stderr );
  else
    fputs( "Other execption!\n", stderr );
  exit( sig + 128 );
}

Obteremos:

 

O fato de fetestexcept() retornar 0 mostra o que eu disse antes. Você pode achar que isso é um problema da glibc, mas não é… Tente com o código abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <x86intrin.h>
 
/* declara o tratador de SIGFPE */
static void myFPEhandler(int);
 
int main(void)
{
  double a, b, c;
 
  // Lê os valores de stdin.
  scanf( "%lg %lg", &a, &b );
 
  signal( SIGFPE, myFPEhandler );
 
  // Tira a máscara da exceção de Divisão por zero.
  _mm_setcsr( _mm_getcsr() & ~(1 << 9) );
 
  c = a / b;
 
  printf( "%g / %g = %g\n", a, b, c );
}
 
void myFPEhandler(int sig)
{
  int except = _mm_getcsr();
 
  fprintf( stderr, "%#x: ", except );
  if ( except & 4 )
    fputs( "Division by zero!\n", stderr );
  fputc( '\n', stderr );
  exit( sig + 128 );
}

Aqui você obterá:

 

Note que o bit 9 de MXCSR está setado (a máscara de divisão por zero) e os 5 bits inferiores estão zerados (os flags indicadores de exceção)… Aqui estou lidando diretamente com SSE, usando as funções intrínsecas _mm_getcsr() e _mm_setcsr(), que o compilador substituirá pelas instruções STMXCSR e LDMXCSR, respectivamente (sem “chamadas” para a glibc), e a mesmíssima coisa acontece (a palavra de controle MXCSR é reajustada pelo sistema operacional ao chamar o tratador de sinal do processo).

Como o kernel “re-seta” as máscaras, elas devem ser desabilitadas de novo dentro do tratador, se você quiser continar tratando as exceções. Isso é simples… mas, o detalhe é que os flags que indicam qual exceção ocorreu também são zerados, antes do tratador ter a chance de obtê-los, e esse problema parece ser incontornável… É possível que exista alguma configuração lá em sys.conf para mudar isso, mas não é prudente fazê-lo. O kernel se comporta dessa maneira para manter o estado do co-processador matemático (80×87) ou SIMD em um padrão coerente (isso está na especificação SysV).

Fonte: https://is.gd/TALkiB

 

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...