Como funciona o "build" em C++

Fica mais fácil entender algumas características de C++ sabendo como arquivos com código-fonte na linguagem são processados e combinados para gerar, finalmente, executáveis e bibliotecas dinâmicas ou estáticas.
0
Considerações?x

Bibliotecas estáticas e dinâmicas

Bibliotecas são blocos de código que são reutilizáveis em vários programas. Usá-las economiza tempo, eliminando a necessidade de reescrever o código várias vezes.

Bibliotecas estáticas (geralmente, em Windows, arquivos com extensão .lib), embora reutilizáveis em vários programas, são bloqueadas em um programa no build. Por outro lado, bibliotecas dinâmicas ou compartilhadas (em Windows, extensão .dll) existem como arquivos separados fora do arquivo executável.

Neste apêndice, apresento alguns fundamentos do processo de build do C++, fundamentais, para que você consiga ser “efetivo” em menos tempo, com menos chances de “atirar no próprio pé”.

Um péssimo trabalhador culpa suas ferramentas.

Provérbios Islandenses

Compilação e linking

Comecemos simples. O build em C++ inclui duas etapas principais: compilaçãolinking.

O processo de compilação processa todos os arquivos contendo código-fonte individualmente e de maneira isolada. Ou seja, para o compilador, quando um arquivo está sendo processado é como se fosse o único, devendo dispor, de alguma meneira, todas as informações necessárias para sua validação.

Obviamente, nenhum “arquivo com código-fonte” é uma ilha. A razão de um sistema ter vários arquivos é, exatamente, dividir o código, segundo algum critério, para facilitar manutenção e evolução, de forma que é natural que código de um arquivo “chame” código (funções e tipos de usuário) em outros arquivos. A “ligação” entre os programas de diversos arquivos com código-fonte é realizada através do processo linking.

O compilador “entende” o código “de cima para baixo”

Como já foi dito, durante a compilação, cada arquivo é processado individualmente e de maneira isolada. Mais do que isso, na medida em que o compilador “lê” um código em um arquivo, precisa conhecer qualquer referência utilizada de maneira a determinar  sua validade e correção.

No código que segue, por exemplo, o compilador consegue “entender” o código porque, não há, em momento algum, informação faltando para validações.

// main.cpp
int doSomething() {
    return 0;
}

int main(){
    auto a = doSomething();
    // ...
}

Já o código abaixo não compila! No momento em que acontece chamada para a função doSomething ela ainda não é “conhecida”.

// main.cpp
int main(){
    auto a = doSomething(); // THIS CODE DOES NOT COMPILE!
    // ...
}

int doSomething() {
    return 0;
}

Para que não nos preocupemos com a ordem em que as funções aparecem no código, a saída é incluir no início “definições” das implementações que aparecerão a seguir. No exemplo que segue, definimos a função doSomething antes de qualquer evocação. Dessa forma, o compilador “sabe” que há uma função com esse nome e, também, consegue determinar corretamente o tipo da variável a.

// main.cpp
int doSomething();

int main(){
    auto a = doSomething();
    // ...
}

int doSomething() {
    return 0;
}

Essa medida, aliás, autoriza “levar” a implementação concreta de doSomething para outro arquivo com código fonte (func.cpp).

// func.cpp
int doSomething() {
    return 0;
}

#include explicado

Incluir no início de um arquivo de código-fonte uma relação exaustiva de declarações de funções e tipos (classes e estruturas) torna o código mais difícil de entender, manter e evoluir. Afinal, caso ocorram breaking changes nas implementações concretas, elas só serão percebidas durante o linking.

O “estilo C++” para lidar com declarações é, para cada arquivo de código-fonte, criar um arquivo de “cabeçalho” relacionando as definições de funções e tipos que ele devem ficar expostas.

// func.h
#ifndef FUNC_H
#define FUNC_H

int doSomething();

#endif /* FUNC_H */

Os arquivos de cabeçalho são, então, “incluídos” em arquivos de código-fonte que serão consumidores.

// main.cpp
#include "func.h"

int main(){
    auto a = doSomething();
    // ...
}

A inclusão, é executada pela diretiva de pré-processamento #include, antes do trabalho do compilador, por um “pré-processador” que, literalmente, deixa o código pronto para a compilação.

Pré-processador

Pré-processamento é uma espécie de “etapa prévia” executada antes da compilação e é executado por um “motor” designado como “pré-processador”.

O pré-processador procura quaisquer diretivas de pré-processamento (linhas de código começando com um #) e altera o código de alguma forma, geralmente adicionando ou removendo linhas.

A diretiva #include, por exemplo, literalmente insere o conteúdo do arquivo especificado na posição em que aparece.

As diretivas #ifndef, #definee #endif ajudam o pré-processador a não “incluir acidentalmente” um mesmo conjunto de definições mais de uma vez.

#pragma once

As diretivas #ifndef, #definee #endifpara impedir duplicidade de inclusão de declarações é um pattern consolidado, aderente a especificação do C++. Entretanto, é uma solução “verbosa” para um problema recorrente.

Atualmente, todos os compiladores do mercado suportam a diretiva #pragma once que atende ao mesmo objetivo do pattern.

// func.h
#pragma once

int doSomething();

Macros

O pré-processador do C++ é tremendamente poderoso e perigoso. Ele “altera” o código fonte, expandindo trechos conforme definições, aparentando “enganosamente” uma função.

#define MAX(a,b) (a > b ? a : b)

#include <iostream>
using namespace std ;

int main() {	
    int x = 10, y = 20;
    cout << "Macro Max(x,y) = " << MAX(x,y) << endl;
    return 0;
}

No exemplo acima, a macro MAX, quando interpretada pelo pré-processador altera o código-fonte fazendo substituição conforme indicado no modelo. Após o pré-processador, o código resultante será como segue:

// ... conteudo de iostream ...
using namespace std ;

int main() {	
    int x = 10, y = 20;
    cout << "Macro Max(x,y) = " << (x > y ? x : y) << endl;
    return 0;
}

Macros são “entregues” pelo pré-processador que não valida, de forma alguma, a correção do código. O exemplo abaixo, por exemplo, não acusa falta de um parêntese na macro. O erro será indicado, só mais tarde, pelo compillador, na linha onde aconteceu a substituição.

#define MAX(a,b) (a > b ? a : b

#include <iostream>
using namespace std ;

int main() {	
    int x = 10, y = 20;
    cout << "Macro Max(x,y) = " << MAX(x,y) << endl;
    return 0;
}

No passado, macros eram utilizadas em demasia e dificultavam consideravelmente a identificação de erros de compilação. Modernamente, os benefícios do inlining proporcionado pelas macros é resolvido de maneira mais eficiente com o modificador inline em funções.

Há bem mais…

Essa é apenas uma breve introdução a como funciona o build do C++. Há muitos detalhes e recursos adicionais que podem e precisam ser explorados.

Compartilhe este capítulo:

Compartilhe:

Comentários

Participe da construção deste capítulo deixando seu comentário:

Inscrever-se
Notify of
guest
2 Comentários
Oldest
Newest Most Voted
Feedbacks interativos
Ver todos os comentários
Dickson
Dickson
2 anos atrás

Excelente, Elemar! Conciso e didático

Ortiz David
Ortiz David
2 anos atrás

Bom dia, Elemar Jr.
Gostei muito do artigo.
Apesar de não ser programador C++, já estudei na faculdade.
Todo conhecimento é bem vindo.
Há poucas semanas, li o outro artigo sobre C++ moderno, onde falava do tipo ‘auto’.
Tenho acompanhado o canal no YouTube, eximiaCo e aprendido sobre DDD e Arquitetura.
Abraços

Elemar Júnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia. 

Desenvolvendo gente que faz a diferença

reconhecida excelência da EximiaCo, em consultorias e assessorias, aplicada no desenvolvimento de competências através de publicações e capacitações abertas e in-company.

TECH

&

BIZ

-   Insights e provocações sobre tecnologia e negócios   -   

55 51 9 9942 0609  |  me@elemarjr.com

55 51 9 9942 0609  |  me@elemarjr.com

bullet-1.png

55 51 9 9942 0609   me@elemarjr.com

2
0
Quero saber a sua opinião, deixe seu comentáriox
()
x