Home RecentChanges

SegFault

Alguns dos erros típicos que cometemos quando programamos em C levam a segmentation fault. Neste documento vamos ver como descobrir e detectar a causa destes erros.

Considere-se o seguinte programa (ficheiro delchar.c):

#include <stdio.h>

#define STRLEN 128

void apagaCaracter(char s[], char c);

int main()
{
  char s[10], c;

  scanf("%c", &c);
  scanf("%s", s);
  apagaCaracter(s, c);
  printf("%s\n", s);

  return 0;
}

void apagaCaracter(char s[], char c)
{
  int i, j;

  for (i = j = 0; j < STRLEN; j++)
    if (s[j] != c) 
      s[i++] = s[j];

  s[i] = s[j];
}

Compilação e execução:

aplf@dozer:~$ gcc -Wall -ansi -pedantic -o delchar delchar.c 
aplf@dozer:~$ ./delchar 
X
aXXdxXa
adxa
Segmentation fault
aplf@dozer:~$

Hum... Segmentation fault? Vamos recompilar com a opção -g para que o binário inclua informação de debugging e vamos utilizar o valgrind:

aplf@dozer:~$ gcc -g -Wall -ansi -pedantic -o delchar delchar.c 
aplf@dozer:~$ valgrind ./delchar 
==21012== Memcheck, a memory error detector
==21012== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==21012== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==21012== Command: ./delchar
==21012== 
X
aXXdxXa
adxa==21012== Conditional jump or move depends on uninitialised value(s)
==21012==    at 0x8048479: apagaCaracter (delchar.c:24)
==21012==    by 0x804843E: main (delchar.c:13)
==21012== 
==21012== Conditional jump or move depends on uninitialised value(s)
==21012==    at 0x8048479: apagaCaracter (delchar.c:24)
==21012==    by 0x804843E: main (delchar.c:13)
==21012== 
adxa
==21012== Jump to the invalid address stated on the next line
==21012==    at 0x104: ???
==21012==  Address 0x104 is not stack'd, malloc'd or (recently) free'd
==21012== 
==21012== 
==21012== Process terminating with default action of signal 11 (SIGSEGV)
==21012==  Bad permissions for mapped region at address 0x104
==21012==    at 0x104: ???
==21012== 
==21012== HEAP SUMMARY:
==21012==     in use at exit: 0 bytes in 0 blocks
==21012==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==21012== 
==21012== All heap blocks were freed -- no leaks are possible
==21012== 
==21012== For counts of detected and suppressed errors, rerun with: -v
==21012== Use --track-origins=yes to see where uninitialised values come from
==21012== ERROR SUMMARY: 65 errors from 3 contexts (suppressed: 13 from 8)
Segmentation fault
aplf@dozer:~$

Portanto temos que conditional jump or move depends on uninitialised value(s) e, como recompilámos com -g, também temos informação a respeito da linha onde ocorre o erro, linha 24 no ficheiro delchar.c. Vamos utilizar o gdb para nos ajudar:

aplf@dozer:~$ gdb ./delchar 
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/aplf/delchar...done.
(gdb) break delchar.c:24
Breakpoint 1 at 0x804846d: file delchar.c, line 24.
(gdb) run
Starting program: /home/aplf/delchar 
X
aXXdxXa

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "aXXdxXa", c=88 'X') at delchar.c:24
24	    if (s[j] != c) 
(gdb) print s[j]
$1 = 97 'a'
(gdb) continue
Continuing.

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "aXXdxXa", c=88 'X') at delchar.c:24
24	    if (s[j] != c) 
(gdb) print s[j]
$2 = 88 'X'
(gdb) continue
Continuing.

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "aXXdxXa", c=88 'X') at delchar.c:24
24	    if (s[j] != c)
(gdb) print s[j]
$3 = 88 'X'
(gdb) continue
Continuing.

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "aXXdxXa", c=88 'X') at delchar.c:24
24	    if (s[j] != c) 
(gdb) print s[j]
$4 = 100 'd'
(gdb) continue
Continuing.

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "adXdxXa", c=88 'X') at delchar.c:24
24	    if (s[j] != c) 
(gdb) print s[j]
$5 = 120 'x'
(gdb) continue
Continuing.

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "adxdxXa", c=88 'X') at delchar.c:24
24	    if (s[j] != c) 
(gdb) print s[j]
$6 = 88 'X'
(gdb) continue
Continuing.

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "adxdxXa", c=88 'X') at delchar.c:24
24	    if (s[j] != c) 
(gdb) print s[j]
$7 = 97 'a'
(gdb) continue
Continuing.

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "adxaxXa", c=88 'X') at delchar.c:24
24	    if (s[j] != c) 
(gdb) print s[j]
$8 = 0 '\000'
(gdb) continue
Continuing.

Breakpoint 1, apagaCaracter (s=0xbffff7b6 "adxa", c=88 'X') at delchar.c:24
24	    if (s[j] != c) 
(gdb) print s[j]
$9 = -3 '\375'
(gdb) quit
A debugging session is active.

	Inferior 1 [process 21072] will be killed.

Quit anyway? (y or n) y
aplf@dozer:~$

Ao pedirmos um break na linha e ficheiro indicados pelo valgrind, vemos que estamos a passar o final da sequência de caracteres dada na entrada. De facto, quando imprimimos o carácter s[j], verificamos que já passámos o '\0' (ver $8 no saída do gdb).

Voltando ao código verificamos que no ciclo while devíamos parar quando chegamos ao final da sequência de caracteres verificando se a posição actual corresponde ao '\0'. Vamos corrigir:

#include <stdio.h>

#define STRLEN 128

void apagaCaracter(char s[], char c);

int main()
{
  char s[10], c;

  scanf("%c", &c);
  scanf("%s", s);
  apagaCaracter(s, c);
  printf("%s\n", s);

  return 0;
}

void apagaCaracter(char s[], char c)
{
  int i, j;

  for (i = j = 0; s[j] != '\0'; j++)
    if (s[j] != c) 
      s[i++] = s[j];

  s[i] = s[j];
}

Compilação e execução:

aplf@dozer:~$ gcc -g -Wall -ansi -pedantic -o delchar delchar.c 
aplf@dozer:~$ ./delchar 
X
aXXdxXa
adxa
aplf@dozer:~$

Podemos em particular ver que o valgrind já não reporta erros:

aplf@dozer:~$ valgrind ./delchar 
==21126== Memcheck, a memory error detector
==21126== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==21126== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==21126== Command: ./delchar
==21126== 
X
aXXdxXa
adxa
==21126== 
==21126== HEAP SUMMARY:
==21126==     in use at exit: 0 bytes in 0 blocks
==21126==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==21126== 
==21126== All heap blocks were freed -- no leaks are possible
==21126== 
==21126== For counts of detected and suppressed errors, rerun with: -v
==21126== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 13 from 8)
aplf@dozer:~$

Notar que este método de depuração não se aplica apenas a este tipo de erros, podemos utilizar esta estratégia para resolver grande parte dos problemas que encontramos quando estamos a programar em C.

Bom trabalho.