Contato

  • Jean Paulo Martins (jeanmartins utfpr edu br)
  • Sala 105, Bloco S (UTFPR - Campus Pato Branco)

Conteúdo

Segmentation Fault

Se seu código está dando falha de segmentação, rode ele dentro do gdb, em muitos casos nem é preciso procurar o erro passo a passo, pois o gdb mostra informações de onde a falha de segmentação ocorreu.

Exemplo 1

@:~$ ./list < input/input02.txt 
[1] 35 
Falha de segmentação (imagem do núcleo gravada)

Esta saída não nos dá informação suficiente para a correção do erro. Portanto, utilizarei o gdb. Primeiro passo, compilar com a flag -g

@:~$ gcc main.c list.c -o list -g

Após isso, inicializar o gdb passando o executável como parâmetro

@:~$ gdb ./list

A seguir, utilizar o comando run do gdb para iniciar a execução do programa.

(gdb) run ./list < input/input02.txt > out
Starting program: ./list < input/input02.txt > out
 
Program received signal SIGSEGV, Segmentation fault.
0x00000000004009fb in list_erase (v=0x7fffffffdfd0, i=1) at list.c:101
101        n->next=j->next;

Desta saída, podemos focar apenas em algumas partes.

Program received signal SIGSEGV, Segmentation fault.
in list_erase (v, i=1) at list.c:101
101        n->next=j->next;

SIGSEGV, é uma constante representando o tipo de erro, neste caso segmentation fault, que se refere à tentativa de acesso de memória não permitido: além dos limites de um vetor, ou em posições de memória que não foram alocadas pelo programador.

Note, que list_erase (v, i=1), nos diz que o erro aconteceu na função list_erase, quando recebendo a posição i=1 e nos mostra também a linha exata onde a falha de segmentação aconteceu.

Exemplo 2

Seguiremos o mesmo procedimento descrito anteriormente.

@:~$ ./list < input/input03.txt 
[1] 68
[2] 68 98
[3] 16 68 98
[2] 68 98
[1] 98
Falha de segmentação (imagem do núcleo gravada)
@:~$ gdb ./list
(gdb) run ./list < input/input03.txt 
[1] 68
[2] 68 98
[3] 16 68 98
[2] 68 98
[1] 98
 
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400acb in list_pop_back (l=0x604020) at list.c:183
183 while(ptr->next->next != NULL)

Neste exemplo, a falha de segmentação ocorreu na linha 183, na função list_pop_back.

in list_pop_back (l=0x604020) at list.c:183
183 while(ptr->next->next != NULL)

Como sabemos, segmentation fault indica acesso indevido de memória. Portanto podemos imaginar que algo errado com a operação

183 while(ptr->next->next != NULL)

O que aconteceria se ptr->next for NULL? Bom, neste caso, ao tentar acessar o próximo ->next, estariamos acessando o endereço indicado por NULL, e lá procurando o campo next, algo como: NULL->next.

Como NULL é usualmente definido como o endereço 0 da memória, qualquer acesso a esse endereço levará à falha de segmentação. Portanto, sempre que houver falha de segmentação, a primeira suspeita deve ser:

  1. Acesso fora dos limites de um vetor
  2. Acesso (dereferenciamento) ao endereço NULL.

Programação estruturada

Função list_size(l)

“Se a estrutura de dados list duplamente encadeada já possui um campo size, qual a necessidade da função list_size().”

Em algumas situações certas funções parecem desnecessárias, é o caso do list_size(). Para entender o porque de sua utilidade, é bom pensarmos em termos da lista como estrutura abstrata, a qual tem uma interface list.h.

Nesse sentido, ambas implementações, “lista simples”, “listas duplas”, teriam uma implementação de list_size(). Nas listas simples, preciso percorrer toda lista para saber o tamanho, nas duplas, basta acessar o size já existente. Algo desse tipo

// forward_list.c
int list_size(list* l) {
     while (ptr != NULL) size++...
     return size;
}
// list.c
int list_size(list* l) {
      return l->size;
}

Agora suponha que estejamos utilizando uma estrutura lista para resolver algum problema, soma/multiplicação de inteiros grandes, splice, por exemplo.

Neste caso, se sempre que precisarmos saber o tamanho de uma lista, utilizarmos list_size() (e as demais funções de acesso a lista), isso nos permitiria utilizar tanto a implementação da lista de encadeamento simples (forward_list.c) quanto a de encadeamento duplo (list.c). Bastando escolher durante a compilação.

Se pelo contrário, eu utilizasse l->size diretamente, eu não conseguiria utilizar a implementação forward_list.c, visto que nela, esse campo não existe.

Portanto, a ideia de termos várias funções para acessar certas propriedades da estrutura, nos permite separar a implementação da definição da estrutura de dados. Isso é sugerido quando programamos pensando em reutilização, e programação de bibliotecas.

Obviamente, podemos também querer implementar uma lista para algo bem específico, sem pensar que tal código será reutilizado para várias coisas diferentes, e então, não precisaríamos nos preocupar com isso.

Em geral é essa a ideia, e se aplica ao uso das demais funções também.