Segmentation fault: causas e debugging
1. O que é um Segmentation Fault?
Segmentation fault (ou segfault) é um erro fatal que ocorre quando um programa tenta acessar uma região de memória que não lhe pertence ou para a qual não tem permissão de acesso. O sistema operacional, através do gerenciador de memória virtual, detecta essa violação e envia o sinal SIGSEGV (signal 11) ao processo.
O comportamento padrão ao receber SIGSEGV é:
- Encerramento abrupto do programa
- Geração opcional de um core dump (arquivo com o estado da memória no momento do crash)
- Mensagem como "Segmentation fault (core dumped)" no terminal
É importante distinguir segmentation fault de outros erros de memória:
- Bus error (SIGBUS): ocorre quando há tentativa de acesso a memória com alinhamento incorreto (ex: ler um int de 4 bytes em um endereço ímpar)
- Abort (SIGABRT): gerado por abort() ou por detecção interna de erro (ex: assert() falho, free() em ponteiro inválido)
2. Causas Comuns em C
Desreferenciamento de ponteiro nulo ou não inicializado
#include <stdio.h>
int main() {
int *p = NULL;
*p = 10; // Segmentation fault!
return 0;
}
Estouro de buffer (stack ou heap)
#include <string.h>
int main() {
char buffer[5];
strcpy(buffer, "esta string é muito longa"); // Estouro de buffer na stack
return 0;
}
Acesso a memória já liberada (use-after-free)
#include <stdlib.h>
int main() {
int *p = malloc(sizeof(int));
free(p);
*p = 42; // Use-after-free! Comportamento indefinido
return 0;
}
Acesso fora dos limites de array
#include <stdio.h>
int main() {
int arr[3] = {1, 2, 3};
printf("%d\n", arr[100]); // Acesso além do limite
arr[-1] = 0; // Índice negativo
return 0;
}
3. Casos Clássicos com Ponteiros
Ponteiro sem alocação
#include <stdio.h>
int main() {
int *p; // Não inicializado
*p = 10; // Comportamento indefinido - provavelmente segfault
printf("%d\n", *p);
return 0;
}
Ponteiro para variável local retornada de função
#include <stdio.h>
int* get_local() {
int x = 42;
return &x; // Retorna endereço de variável local (na stack)
}
int main() {
int *p = get_local();
printf("%d\n", *p); // Use-after-scope! Pode causar segfault
return 0;
}
Dupla liberação (double free)
#include <stdlib.h>
int main() {
int *p = malloc(sizeof(int));
free(p);
free(p); // Double free - corrompe o gerenciador de heap
return 0;
}
Aritmética de ponteiro incorreta
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello";
char *p = str;
// Avança além do terminador nulo
p = p + 10;
printf("%c\n", *p); // Acesso a memória inválida
return 0;
}
4. Erros com Arrays e Strings
Falta do terminador nulo
#include <stdio.h>
#include <string.h>
int main() {
char buffer[5] = {'H', 'e', 'l', 'l', 'o'}; // Sem '\0'!
printf("%s\n", buffer); // Pode causar segfault ou lixo
return 0;
}
Uso de gets() - função insegura
#include <stdio.h>
int main() {
char buffer[10];
gets(buffer); // PERIGOSO: sem limite de tamanho
return 0;
}
Confusão entre sizeof e strlen
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char *str = "Hello";
char *copy = malloc(sizeof(str)); // sizeof(str) = 8 (tamanho do ponteiro!)
// char *copy = malloc(strlen(str) + 1); // Correto
strcpy(copy, str); // Pode causar estouro de buffer
free(copy);
return 0;
}
5. Debugging com GDB: Primeiros Passos
Compilação adequada
gcc -g -O0 programa.c -o programa
Execução no GDB e captura do crash
$ gdb ./programa
(gdb) run
Starting program: ./programa
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555156 in main () at programa.c:8
8 *p = 10;
Comando backtrace para ver a pilha
(gdb) backtrace
#0 0x0000555555555156 in main () at programa.c:8
Inspeção de variáveis
(gdb) print p
$1 = (int *) 0x0
(gdb) info locals
p = 0x0
6. Técnicas Avançadas de Debugging
Breakpoints condicionais
(gdb) break main.c:15 if i == 1000
(gdb) run
Análise de core dump
$ ulimit -c unlimited # Habilitar core dump
$ ./programa
Segmentation fault (core dumped)
$ gdb ./programa core
(gdb) backtrace
Uso do Valgrind (memcheck)
$ valgrind --tool=memcheck ./programa
==12345== Invalid write of size 4
==12345== at 0x109156: main (programa.c:8)
==12345== Address 0x0 is not stack'd, malloc'd or (recently) free'd
Uso do AddressSanitizer
$ gcc -g -fsanitize=address programa.c -o programa
$ ./programa
=================================================================
==12345==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
Watchpoints para monitorar variáveis
(gdb) watch *ptr
(gdb) continue
7. Prevenção e Boas Práticas
Inicialização explícita de ponteiros
int *p = NULL; // Sempre inicializar
if (p != NULL) {
*p = 10;
}
Verificação do retorno de malloc/calloc
int *arr = malloc(100 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "Falha na alocação\n");
exit(1);
}
Limitação de acesso a arrays
#define TAMANHO 10
int arr[TAMANHO];
for (int i = 0; i < TAMANHO; i++) { // Usar constante, não valor fixo
arr[i] = i;
}
Adoção de funções seguras
// Em vez de gets(buffer)
fgets(buffer, sizeof(buffer), stdin);
// Em vez de strcpy(dest, src)
strncpy(dest, src, tamanho_dest - 1);
dest[tamanho_dest - 1] = '\0';
// Em vez de sprintf
snprintf(buffer, sizeof(buffer), "%s %d", str, num);
Referências
-
GDB Documentation - Debugging Segfaults — Documentação oficial do GDB sobre tratamento de sinais como SIGSEGV e comandos para debugging de segmentation faults.
-
Valgrind User Manual - Memcheck — Guia completo da ferramenta Memcheck do Valgrind para detectar violações de memória, incluindo uso após liberação e estouro de buffer.
-
AddressSanitizer - Google — Documentação oficial do AddressSanitizer, ferramenta de detecção de erros de memória em tempo de compilação.
-
CERT C Coding Standard - Memory Management — Padrões de codificação segura em C da CMU/SEI, com regras específicas para evitar segmentation faults em gerenciamento de memória.
-
Linux man page - signal(7) — Página de manual do Linux sobre sinais, incluindo detalhes técnicos sobre SIGSEGV e core dumps.