Jump to content
Sign in to follow this  
fredericopissarra

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

Recommended Posts

Posted (edited)

 

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

 

Edited by Fernando Mercês
Favor colocar o corpo do texto aqui na próxima. É importante pra quem tá lendo já ler o conteúdo. Deve-se manter a fonte, claro. 😉
  • Agradecer 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...