Passo 1 - Encontrando o problema (Parte 3)

Recursos do Minicurso

Depuradores

Depurar código é um problema tão comum que existem programas feitos especialmente para ajudar outros programadores a depurar com mais eficiência. Eles são chamados de depuradores, e existem muitos depuradores que funcionam com a linguagem C. Vamos dar uma olhada no gdb, um depurador comum usado no terminal.

Abrir Replit

Para nossos exemplos, vamos usar o algoritmo Quicksort.

Quicksort é um algoritmo que ordena um array selecionando primeiro um elemento do array como pivô.

Em seguida, os elementos são organizados de acordo com uma das condições abaixo:

Depois que a ordenação é feita, o mesmo processo é chamado recursivamente nas partes superior e inferior do array, usando o pivô como ponto de referência.

Nossa versão do quicksort assume que o elemento mais à esquerda é o “maior” elemento e o mais à direita é o pivô dentro da partição.

Quicksort usando o pivô como o elemento mais à direita.
Figura 1: Quicksort usando o elemento mais à direita como pivô e assumindo que o elemento mais à esquerda é o “maior” elemento.

GDB (GNU Project Debugger) é um depurador poderoso que permite depurar programas a partir da linha de comando, o que é útil em casos onde você não tem acesso a uma interface gráfica (GUI).

É importante entender como o programa funciona para depurar de forma eficaz. Nossa implementação de ‘quicksort’ executa uma implementação recursiva de quicksort e realiza a ordenação se o elemento atual for menor que o pivô, assumindo que o primeiro elemento é o “maior” elemento. A ordenação em si ocorre na função partition.

Passos para depurar com GDB

Compile o programa

  1. Abra a aba Shell e compile o programa:
make Quicksort

Quando o programa é compilado, as flags: -g e -Og são usadas. A primeira indica ao compilador que adicione informações de depuração, o que significa que sem essa flag, o gdb não seria capaz de depurar o programa. A segunda diz ao compilador para otimizar o programa de uma forma que não afete a estrutura de execução. Sem -Og, o compilador poderia otimizar parte do seu código, tornando o depurador menos eficaz.

É importante lembrar da segunda flag. Para depuração, você deve SEMPRE garantir que o compilador faça otimizações mínimas no seu código, pois otimizações podem alterar drasticamente como o código é executado!

Execute o programa

  1. Digite o comando gdb examples/Quicksort. Isso abrirá a interface de linha de comando do GDB. Para depurar um programa com gdb você pode usar gdb <nome do programa>.

  2. Certifique-se de que o GDB exibe Reading symbols from ./examples/Quicksort..., caso contrário você não anexou o programa ao GDB.

  3. Você pode sair do GDB digitando o comando quit (ou qualquer prefixo: q funciona) como se fosse um comando normal do shell.

Você deve ver algo como isto:

Executando GDB no arquivo ‘Quicksort’.
Figura 2: Executando GDB no arquivo ‘Quicksort’.

Depurando o programa

Para depurar o programa, precisamos executá-lo a partir do GDB.

  1. Digite o comando run (ou r). Isso executará o programa como se você o tivesse rodado pelo terminal normal.
(gdb) run

O programa primeiro imprime o conteúdo do array a ser ordenado: um array de números desordenados. Em seguida, ele executa o algoritmo de ordenação e, por fim, imprime o array ordenado. Você pode ver como os elementos mudam de posição durante a ordenação!

No entanto, parece que a ordenação não está acontecendo como esperado.

Vamos usar uma das ferramentas mais importantes que os depuradores oferecem: breakpoints. Um breakpoint informa ao depurador para pausar o programa sempre que atingir aquela linha de código. Isso permite que você veja o que está acontecendo no programa em tempo real. Note que o depurador não executa a linha onde o breakpoint está até você continuar a execução.

Primeiro vamos identificar onde a ordenação dos elementos acontece. Você sabe onde isso está acontecendo?

Resposta

  1. Coloque um breakpoint onde a ordenação acontece usando a sintaxe break <arquivo:linha>.
Resposta

  1. Rode o programa com run e observe como ele para ao chegar no breakpoint.

  2. Enquanto o programa está pausado, você pode ver os valores das variáveis. Teste alguns comandos print para avaliar expressões. Aqui está um exemplo de como usar print:

# você pode imprimir o valor de uma variável
print minhaVariavel

# você pode criar expressões mais elaboradas
print minhaVariavel + 2 
  1. Você pode avançar manualmente no código usando next (ou n), que fará o depurador avançar para a próxima linha de código sem entrar em uma função.

  2. O comando step move o depurador para a primeira linha de código dentro de uma chamada de função.

Você precisa encontrar as variáveis com problema que estão fazendo o programa não funcionar como esperado. Tente ver se você consegue identificar o problema!

Dica: Observe como as variáveis no loop mudam.
Resposta

Note que todos os depuradores compartilham os mesmos conceitos básicos: eles permitem que você avance pelo seu código em tempo real. Você deve quase sempre usar depuradores em vez de apenas print. Você não vai se arrepender!

Comandos do GDB

Existem muitos comandos que o GDB oferece. Esta tabela resume alguns úteis, mas também recomendamos usar esta GDB Reference Sheet para ver mais comandos.

ComandoO que faz
gdb <nome do programa>Executa/carrega o programa com o GDB
run ou rExecuta o programa carregado
quit ou qSai do GDB
break <arquivo:linha>Cria um breakpoint na linha especificada do arquivo
print <nome da variável> ou print <expressão>Exibe o valor de uma expressão que pode conter variáveis e constantes
next ou nAvança para a próxima linha de código sem entrar em uma função
stepAvança para a primeira linha de código dentro de uma função
continue ou cContinua executando o código até o próximo breakpoint
delete <nº do breakpoint> ou d <nº do breakpoint>Deleta o breakpoint na linha especificada
backtrace ou btExibe a pilha de chamadas (stack trace) mostrando quais funções foram chamadas até a linha atual