Organizar e depurar programas

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

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

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 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

SintomaFase provávelO que conferir
Tipo ou nome desconhecidoCompilaçãoDeclaração, include, namespace e assinatura
Referência não definidaLigação.cpp no alvo, definição e tipos iguais
Símbolo definido várias vezesLigaçãoDefinições repetidas e conteúdo de cabeçalhos
Resultado erradoExecuçãoEntrada, contrato, estado e caminho seguido
Falha de memóriaExecuçãoLimites, 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
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
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

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.