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.