# Organizar e depurar programas

Cabeçalhos, definições, ligação, CMake, CLion e um procedimento de depuração.

Página: https://resumos.rgo.pt/cadeiras/p/organizar-programas/

Uma declaração de `dobro` permite compilar a chamada sem mostrar o seu corpo. A definição pode ficar noutro ficheiro, mas precisa de entrar na ligação para formar o executável.

Esta página segue esses dois percursos separados até se juntarem.

## Cabeçalhos e implementação

Para separar uma função que duplica um valor real, cria estes três ficheiros na mesma pasta.

`dobro.hpp` declara a interface. A guarda impede repetir o conteúdo do cabeçalho na mesma unidade de tradução.

```
#ifndef DOBRO_HPP
#define DOBRO_HPP

double dobro(double valor);

#endif
```

`dobro.cpp` define a operação e inclui o próprio cabeçalho, por isso o compilador deteta muitas divergências entre declaração e definição.

Não garante tudo: se o cabeçalho declara `double dobro(double);` e o ficheiro define `double dobro(int v)`, estás a declarar outra sobrecarga e a definição esperada continua em falta, por isso a ligação falha. Mantém declaração e definição com a mesma assinatura.

```
#include "dobro.hpp"

double dobro(double valor) {
    return 2.0 * valor;
}
```

`main.cpp` usa a interface sem reproduzir a implementação:

```
#include "dobro.hpp"
#include <iostream>

int main() {
    std::cout << dobro(4.0) << "\n";
}
```

Este programa escreve `8`. A saída continua igual se reorganizares a implementação sem alterar o contrato.

## Compilação e ligação

Cada `.cpp`, depois do pré-processamento dos seus `#include`, forma uma **unidade de tradução**. A compilação produz ficheiros objeto. A ligação reúne-os e resolve as referências entre eles.

Neste exemplo há duas unidades, `dobro.cpp` com a definição e `main.cpp` com a chamada. Cada uma compila sozinha. Só a ligação as junta.

```
g++ -std=c++17 -Wall -Wextra -Wpedantic -c dobro.cpp -o dobro.o
g++ -std=c++17 -Wall -Wextra -Wpedantic -c main.cpp -o main.o
g++ main.o dobro.o -o exemplo
./exemplo
```

![O cabeçalho dobro.hpp é incluído em main.cpp e dobro.cpp. Cada fonte compila num ficheiro objeto. A ligação reúne main.o e dobro.o no executável exemplo.](https://resumos.rgo.pt/cadeiras/p/organizar-programas/figura-1.svg)

O cabeçalho entra nos dois percursos, mas não produz sozinho a definição compilada de `dobro`, por isso se faltar o ramo de `dobro.o` a ligação fica com uma chamada por resolver.

Também podes compilar e ligar de uma vez com `g++ -std=c++17 main.cpp dobro.cpp -o exemplo`. Compilar só `main.cpp` encontra a declaração, mas a ligação falha por faltar a definição de `dobro`. O erro típico é `undefined reference to 'dobro(double)'`: a compilação passou, a ligação não encontrou o código.

Uma definição não `inline` de função colocada num cabeçalho incluído por vários `.cpp` pode criar múltiplas definições na ligação. Coloca a implementação num `.cpp`, ou usa a regra adequada para funções `inline` e templates. Não incluas ficheiros `.cpp` para resolver um erro de ligação; acrescenta-os ao alvo de compilação.

## CMake para vários ficheiros

Guarda este `CMakeLists.txt` junto das fontes:

```
cmake_minimum_required(VERSION 3.16)
project(exemplo LANGUAGES CXX)
add_executable(exemplo main.cpp dobro.cpp)
target_compile_features(exemplo PRIVATE cxx_std_17)
set_target_properties(exemplo PROPERTIES CXX_EXTENSIONS OFF)
```

Na pasta do projeto, corre:

```
cmake -S . -B build
cmake --build build
./build/exemplo
```

`-S` escolhe a pasta das fontes e `-B` a pasta dos ficheiros de build. Não edites os ficheiros gerados dentro de `build`. A localização do executável pode variar num gerador com várias configurações, como Debug e Release. No CLion, abre a pasta com `CMakeLists.txt`, confirma a toolchain e escolhe o alvo `exemplo`.

## Localizar a fase do erro

| Sintoma | Fase provável | O que conferir |
| --- | --- | --- |
| Tipo ou nome desconhecido | Compilação | Declaração, include, namespace e assinatura |
| Referência não definida | Ligação | `.cpp` no alvo, definição e tipos iguais |
| Símbolo definido várias vezes | Ligação | Definições repetidas e conteúdo de cabeçalhos |
| Resultado errado | Execução | Entrada, contrato, estado e caminho seguido |
| Falha de memória | Execução | Limites, duração de vida e propriedade |

Lê a primeira mensagem relevante, porque um erro de sintaxe pode causar muitas mensagens seguintes. Um erro de ligação não se resolve na entrada do programa, pois a execução ainda não começou.

## Usar o depurador

Compila com `-g -O0` ou seleciona Debug no CLion. Para o erro de remoção com dois negativos consecutivos, coloca um breakpoint na linha do `erase` e corre com `{-1, -2}`, observando o iterador devolvido, que aponta para `-2`, ainda por analisar; se o código avançar outra vez sem ler esse elemento, `-2` sobrevive por engano. Compara argumentos e variáveis com o que o contrato exige.

**Step into** entra na função chamada; **step over** executa-a sem abrir o seu corpo; **step out** termina a chamada atual. A pilha de chamadas mostra como chegaste à linha. Ao seguir apontadores, distingue o valor do endereço do conteúdo apontado e confirma que o objeto ainda vive.

## Um procedimento para corrigir erros

Primeiro, reduz o problema a uma entrada pequena com saída conhecida; depois identifica a primeira operação em que o estado diverge do esperado. Corrige essa operação, volta a correr a mesma entrada e acrescenta um caso que detete a causa, e só então testa fronteiras próximas.

Se uma função remove negativos mas perde também o elemento seguinte, segue dois negativos consecutivos, pois esse caso revela se o iterador é avançado indevidamente depois de `erase`; dez testes com positivos não esclarecem esse erro. Usa avisos, sanitizers e testes em conjunto, pois cada um procura falhas diferentes.

## Exercícios

A declaração não é a implementação

`main.cpp` inclui `dobro.hpp` e chama dobro. `dobro.cpp` contém a definição, mas o alvo só compila `main.cpp`. A compilação de `main.cpp` resulta e a ligação falha. Qual é a correção?

Pista

O compilador conhece a assinatura, mas o executável continua sem o código.

Solução

Acrescenta `dobro.cpp` ao alvo, por exemplo `add_executable(exemplo main.cpp dobro.cpp)`. O `include` traz a declaração para a unidade de tradução; não liga automaticamente o outro .cpp.

Confere a tua resposta:

*   Localizei a falha na ligação.
*   Acrescentei dobro.cpp ao alvo.
*   Distingui include da ligação dos ficheiros objeto.

Erros frequentes

Não incluas `dobro.cpp` nem copies a definição para todos os cabeçalhos para contornar a ligação.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/p/organizar-programas/#compila%C3%A7%C3%A3o-e-liga%C3%A7%C3%A3o)

Reduzir um erro de remoção

Uma função que remove negativos falha quando há dois consecutivos. Indica uma entrada mínima, o resultado esperado e o estado a observar no depurador.

Pista

Conserva só o padrão que desencadeia o erro.

Solução

Uma entrada mínima é `{-1, -2}` e o resultado esperado é o vetor vazio. Observa o iterador devolvido por erase e verifica se o código o incrementa novamente antes de analisar o segundo negativo. Um caso como `{1, -1, -2, 3}` ajuda depois a confirmar que os restantes valores se conservam.

Confere a tua resposta:

*   Escolhi dois negativos consecutivos.
*   Derivei o resultado vazio do contrato.
*   Indiquei o iterador devolvido por erase como estado relevante.

Erros frequentes

Testar apenas negativos separados por positivos pode esconder o avanço indevido.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/p/organizar-programas/#um-procedimento-para-corrigir-erros)

Incluir o cabeçalho não define a função

O cabeçalho declara `double dobro(double)`, mas `dobro.cpp` define `double dobro(int)`. `main.cpp` inclui o cabeçalho e chama `dobro(4.0)`. A compilação passa e a ligação falha. Porquê? Como corrigir?

Pista

Os tipos dos parâmetros fazem parte da identidade da função.

Solução

A definição com `int` é outra sobrecarga e a versão com `double` continua sem definição. A ligação responde `undefined reference to dobro(double)`. Corrige a assinatura em `dobro.cpp` e confirma que ambos os ficheiros participam no alvo. Não incluas o `.cpp` para contornar a ligação.

Confere a tua resposta:

*   Expliquei por que o include não liga o .cpp.
*   Identifiquei a sobrecarga como outra função.
*   Corrigi o tipo do parâmetro na definição e confirmei o alvo.

Erros frequentes

Não copies a definição para o cabeçalho sem inline. Cria múltiplas definições na ligação se vários ficheiros o incluírem.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/p/organizar-programas/#cabe%C3%A7alhos-e-implementa%C3%A7%C3%A3o)
