Makefile em C++ – compile sem medo!

Makefile em C++

Se você já teve que compilar um programa em C++ que contém vários arquivos “na mão”, você deve saber que essa tarefa pode ser um tanto penosa (a menos que você trapaceie e compile todos os .cpp de uma vez só em um arquivo de saída. Quem nunca fez isso, não é mesmo? 😝). Você deve também ter ficado muito feliz quando descobriu o Makefile – uma tremenda mão na roda, né? Pois bem, falarei nesse artigo sobre os Makefiles e sobre como eles simplificam muito a vida do programador de C++.

Compilação Manual

Primeiramente, sem a ajuda do Makefile é preciso gerar os arquivos .o (as chamadas bibliotecas estáticas, do inglês static libraries) um por um e depois juntá-los todos em seu executável final. Se seu projeto tiver um ou dois arquivos de código fonte, ça va, mas se esse não for o caso tal tarefa pode rapidamente se tornar tediosa e consumir muito do seu tempo e energia.

Na figura a seguir eu mostro como compilar dois arquivos individualmente, e depois unir os arquivos resultantes .o em um único executável. O primeiro arquivo se chama greetings.cpp e o segundo main.cpp – eles não fazem nada além de imprimir “Hello, friend!” no terminal (através da chamada da função greetings() definida no arquivo greetings.cpp), bastante simples. Como você pode ver, é preciso executar compilar cada arquivo individualmente usando o g++ com a flag -c e depois unir os arquivos .o usando o g++ com a flag -o. Imagine fazer isso 100 vezes, ou 1000 vezes – não parece muito legal, não é?

Figura 1 – compilação e linkagem “manuais” dos arquivos .cpp e .o

Compilação com Makefile

Felizmente, podemos usar os Makefiles, arquivos que descrevem como o nosso programa desse ser construído (compilado e linkado – isto é, ter suas partes conectadas) e que nos permitem gerar nosso binário final com um simples comando como “make all”. Parece bom, não? E é! Na prática, a coisa funciona como descreverei nas seções que se seguem, e usarei de base para a explicação o Makefile da figura abaixo.


Makefile do projeto

Makefile para c++
Makefile usado de exemplo no nosso projeto

Estrutura do projeto

No projeto que criei de exemplo, que é bastante simples, eu possuo apenas dois arquivos .cpp e um .hpp: greetings.hpp (com a declaração da função que utilizo no arquivo main.cpp), greetings.cpp (com a definição da função usada na main) e main.cpp; juntos eles não fazem nada mais que imprimir a mensagem “Hello, friend!” na saída do programa (aqui vai o link do projeto no github).

Até aqui tudo bem, mas e aí, como uso o tal do Makefile?

Pré-requisitos para o uso do Makefile

Antes de tudo precisamos ter o programa make instalado (o procedimento de instalação é bastante simples em todas as plataformas), e criar um arquivo chamado Makefile na pasta do seu projeto. Além disso, se você usa Linux ou MacOS é muito provável que você já tenha o make instalado por padrão. Ainda assim, se você não souber por onde começar e precisar baixar o make no Windows, por exemplo, aqui vai o link para download do make (infelizmente a página está disponível apenas em inglês).

Estrutura do Makefile

Uma vez instalado o make, a primeira coisa a se fazer é dizer quais arquivos .o (bibliotecas estáticas) serão gerados para cada um dos nossos arquivos .cpp (considerando que cada um deles fornece uma única funcionalidade e não deve ser agrupado com outros como em uma grande biblioteca). No exemplo, nomeei main o comando que gera o arquivo main.o, e greetings o arquivo que gera greetings.o. Note que para cada um desses comandos, eu preciso dizer de quais arquivos o comando depende (no meu caso são os arquivos main.cpp e greetings.cpp que aparecem após o nome dos comandos main e greetings, respectivamente). Ok, mas como isso funciona? É bastante simples, vejamos a seguir.

Quando eu digito make main no terminal, o programa make executa para mim o comando que eu lhe disse para executar; nesse exemplo, o comando é g++ $(CFLAGS) -c main.cpp. Veja também que CFLAGS é definido na primeira linha do Makefile como uma sequência de flags de compilação. Assim, quando eu digitar make main no terminal, o comando equivalente que será executado é g++ -Wall -Werror -Wextra -pedantic -std=c++20 -c main.cpp. O mesmo vale para o comando make greetings, exceto que ele irá usar o arquivo greetings.cpp ao invés do main.cpp. Legal, não? Sim, mas isso não é tudo! O Makefile também detecta automaticamente se nada mudou no seu arquivo .cpp desde a última compilação, e se esse for o caso o comando make greetings, por exemplo, não fará nada.

Por fim, podemos criar um comando que une todos os arquivos .o em um arquivo executável final – este será o nosso programa de fato, que poderemos rodar. No meu exemplo eu chamei esse comando que faz a conexão entre todos os arquivos .o de final. Portanto, ao digital make final no terminal, o make invocará o g++ para gerar o meu programa completo, como se eu tivesse digitado no terminal o comando g++ -Wall -Werror -Wextra -pedantic -std=c++20 main.o greetings.o -o final. Após isso basta digitar ./final e voilà, o programa é executado e eu vejo na saída meu tão esperado “Hello, friend!”.

Bonus – make clean

Outra coisa legal dos Makefile é o fato de podermos criar comandos adicionais, além daqueles usados para a compilação dos nossos arquivos, como por exemplo o make clean que remove todos os arquivos .o do nosso projeto. Acredite, essa astúcia te poupará bastante tempo se o seu projeto for grande o suficiente.

Vantagens do uso do Makefile

Se você chegou até aqui, você pode ter achado isso bastante legal, mas ainda assim pode estar se perguntando o seguinte: “Tudo bem, legal, mas quais são as vantagens reais de utilizar o Makefile ao invés de compilar os arquivos eu mesmo, na mão?”. Para responder-te, irei resumir a seguir as principais razões para se usar os Makefiles.

  1. Praticidade: ao invés de compilar todos os arquivos de .cpp para .o, um por um, o Makefile te permite definir um comando que compilará todos os arquivos de uma só vez, e ainda unirá todos os arquivos .o ao final para gerar o executável do seu programa.
  2. Velocidade de compilação: os Makefiles te permitem poupar tempo de compilação, pois eles recompilam apenas aqueles arquivos que foram modificados desde a última compilação. O ganho de tempo é tanto maior quanto mais arquivos você tiver no seu projeto.
  3. Legibilidade: com o Makefile, você pode facilmente ver quais arquivos são utilizados para gerar um .o qualquer, e também pode ver rapidamente quais flags de compilação são utilizados, por exemplo. De modo geral, o Makefile torna mais fácil entender o que está acontecendo durante a compilação do seu programa.
  4. Controle e previsibilidade: o uso dos Makefiles torna a compilação do seu programa mais previsível, uma vez que reduz ou elimina os riscos de que o programa seja compilado com uma flag diferente ou de um modo inadequado, pois os comandos de compilação permanecem fixos (a menos que você os altere, evidentemente). Ao compilar vários arquivos na mão, talvez você se engane ou use uma flag que não queria, o que dificilmente acontecerá se você usar um makefile.

Conclusão

Neste artigo eu falei sobre como o Makefile pode facilitar a vida do programador na hora de compilar os seus projetos, e como a sua utilização lhe poupa trabalho e tempo.

Primeiro falei resumidamente do fardo que é compilar vários arquivos diferentes em C++ manual e individualmente, e depois mostrei como o uso dos Makefiles simplifica esse processo através da criação de comandos individuais usados para compilar cada arquivo .cpp (gerando arquivos .o na saída), e por fim um comando final que une todos os arquivos .o gerados pela primeira etapa de compilação em um arquivo executável de saída (este pode ser tabém uma biblioteca dinâmica .so ao invés de um executável – mas não toquei nesse assunto neste artigo). Falei também da vantagem do Makefile em não recompilar arquivos .cpp que não sofreram alterações, tornando o processo inteiro de compilação muito mais rápido (sobretudo para grandes projetos).

Gostou do artigo? Então inscreva-se na nossa newsletter para não perder nenhum dos nossos artigos 🙂

Foto de perfil de Emanoel

Sou apaixonado por tecnologia, literatura e também filosofia. O cultivo dessas paixões ao longo da minha trajetória me inspiraram a compartilhar aquilo que aprendo com os outros da maneira mais clara que eu possa. Sou formado em Engenharia Elétrica no Brasil, e também sou engenheiro formado na França. Trabalho atualmente como programador C++ em uma multinacional francesa, uma das maiores empresas de TI do mundo.

Leave a Reply

Your email address will not be published. Required fields are marked *