Técnicas para desenvolvimento e aceleração de códigos científicos
|
Nesta atividade, exercitaremos como utilizar o programa GDB para descobrir problemas em programas de computador.
Cada atividade possui um pequeno arquivo de código que exemplifica alguns dos problemas que podem ser facilmente depurados através do GDB.
Problemas estes, que compiladores não avisam e que muitas vezes passam despercebidos até que algo estranho aconteça com o programa, por exemplo:
O que é uma falha de segmentação?
A falha de segmentação é uma tentativa de acesso (leitura ou escrita) a um endereço de memória inválido (por exemplo, um ponteiro = NULL)
ou que não possui as permissões devidas (por exemplo, tentativa de escrita em memória read-only).
Essas falhas são bem dificeis de resolver, pois muitas vezes elas acontecem somente com algumas "entradas de dados" específicas.
O exemplo abaixo simula uma falha de segmentação ao acessar um ponteiro NULL. Vamos depurar esse programa.
Comandos para compilar e executar o programa, através do GDB:
g++ -g segfault.cpp -o segfault gdb ./segfault
Exercitaremos os comandos run, break, print, set variable e continue
programa: segfault.cpp
Este exemplo é similar ao anterior, porém vamos depurar um programa com múltiplos arquivos.
Comandos para compilar e executar o programa, através do GDB:
g++ -g multiplos_arquivos.cpp -o multiplos_arquivos gdb ./multiplos_arquivos
Exercitaremos os comandos run, break, print, set variable, backtrace, frame e continue
programa: multiplos_arquivos.cpp
classe1: class1.h
classe2: class2.h
O que são rotinas recursivas?
São rotinas que chamam elas mesmas dentro de seu código até que uma condição de parada aconteça.
E se essa condição não acontecer?
Acontece o que chamamos de 'estouro de pilha' ou Stack overflow.
Como funciona a recursão internamente?
Cada vez que uma subrotina é chamada o endereço para o qual ela deve retornar quando acabar é guardado em uma pilha.
Quando muitas chamadas acontecem, por exemplo no caso da recursão, esse espaço irá acabar e o programa aborta com
a exceção de estouro de pilha. Também pode haver estouro de pilha quando as rotinas recursivas possuem variáveis locais
muito grandes, que ajudam a esgotar o espaço da pilha.
(Por exemplo, tente adicionar no programa dentro da rotina recursiva o seguinte código: double boom[1000000];)
Nesse exemplo, baseado na rotina de cálculo fatorial recursivo, vamos simular um estouro de pilha e depurá-lo usando o GDB.
Comandos para compilar e executar o programa, através do GDB:
g++ -g stackoverflow.cpp -o stackoverflow gdb ./stackoverflow
Exercitaremos os comandos run, backtrace, break, display, return e continue
programa: stackoverflow.cpp
E agora? Meu código não está retornando valor certo de fatorial, como resolver?
Comandos para compilar e executar o programa, através do GDB:
g++ -g fatorial.cpp -o fatorial $ ./fatorial Entre com um numero para calcular seu fatorial: 4 O fatorial de 4 = 0
Exercitaremos os comandos run, list, break, set variable e continue
programa: fatorial.cpp
Comandos utilizados para compilar os exemplos através do uso do CMake:
############ 1) Fazer download e Salvar todos os .h e .cpp dentro de um diretório. (acima) ############ 2) Fazer download e Salvar o CMakeLists.txt dentro desse mesmo diretório. cmake . make