Skip to content

Instantly share code, notes, and snippets.

@flisboac
Last active September 17, 2019 18:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flisboac/735568711789584e7afd89cd8c4b8b23 to your computer and use it in GitHub Desktop.
Save flisboac/735568711789584e7afd89cd8c4b8b23 to your computer and use it in GitHub Desktop.

O que segue é um breve resumo, aplicável a ambos C e C++, na medida do possível.

Em C e C++, há o conceito de translation unit (TU) (C, C++). Cada arquivo que vc compila para um arquivo-objeto (com extensao .o, ou no Windows com extensao .obj), é oriundo de uma TU.

Primeiro, sua TU é preprocessada -- é neste estágio que o seu arquivo externo é incluído, via diretiva de processador #include (C, C++). Uma vez preprocessada, sua TU é compilada para um arquivo-objeto. Ao contrário do Python, que considera "arquivos externos" como módulos, a inclusão de arquivos no C++ é feita no pré-processamento, e por isso é uma inclusão meramente textual.

Depois o linker une todos os arquivos-objeto em um único binário, seja executável ou não.

No C/C++, também é possível separar a declaração (C, C++) da sua função de sua definição(C, C++). Como regra geral, tanto para o C quanto para o C++ (e apesar das muitas diferenças entre as duas linguagens), você pode declarar sua função quantas vezes forem necessárias, contanto que:

  1. Todas as suas declarações possuam a mesma assinatura; e
  2. Apenas uma definição seja visível pelo linker dentre todas as suas TUs (em C++, chamamos esta regra de ODR).

Para funções, a declaração é essencialmente a assinatura da função. Já a definição é onde vc coloca a implementação (o corpo) da função.

Tome como exemplo o seguinte cenário. Vc tem um arquivo que faz simplesmente a soma de dois números aleatórios e mostra-os na tela.

Arquivo main.c:

#include <stdio.h>

int add(int a, int b) {
  return a + b;
}

int main(void) {
  int ra, rb, sum;
  printf("ra = "); scanf("%d", &ra);
  printf("rb = "); scanf("%d", &rb);
  sum = add(ra, rb);
  printf("%d + %d = %d\n", ra, rb, sum);
}

Neste caso, o main.c é sua única TU. Um arquivo como estes pode ser facilmente compilado pelo e.g. GCC da seguinte forma:

gcc -o main.exe main.c

Ou de forma menos implícita:

# Gerar o arquivo-objeto para main.c
gcc -c -o main.o main.c
# Gerar o executável final
gcc -o main.exe main.o

Digamos então que vc queira migrar a função add para um arquivo separado. Isso é simples: declare a assinatura da função na sua TU, e implemente-a em outra TU (outro arquivo). O linker tratará de encontrar a definição da sua função.

Arquivo main.c:

#include <stdio.h>

int add(int a, int b);

int main(void) {
  int ra, rb, sum;
  printf("ra = "); scanf("%d", &ra);
  printf("rb = "); scanf("%d", &rb);
  sum = add(ra, rb);
  printf("%d + %d = %d\n", ra, rb, sum);
}

Arquivo add.c:

int add(int a, int b) {
  return a + b;
}

Agora, vc precisará compilar os seus dois TUs separadamente, e fazer o link de ambos ao final:

# Gerar os arquivo-objeto para main.c e add.c
gcc -c -o main.o main.c
gcc -c -o add.o add.c

# Gerar o executável final
gcc -o main.exe main.o add.o

Opcionalmente, você poderá deixar a definição das suas funções em um arquivo separado, que será incluído em todos os lugares nos quais a sua função deva ser utilizada. Neste caso, criamos um arquivo header, que geralmente tem sufixo .h. Vamos refatorar nossa solução:

Arquivo add.h:

int add(int a, int b);

Arquivo add.c:

#include "add.h"

int add(int a, int b) {
  return a + b;
}

Arquivo main.c:

#include <stdio.h>

#include "add.h"

int main(void) {
  int ra, rb, sum;
  printf("ra = "); scanf("%d", &ra);
  printf("rb = "); scanf("%d", &rb);
  sum = add(ra, rb);
  printf("%d + %d = %d\n", ra, rb, sum);
}

A compilação desta solução continua a mesma:

# Gerar os arquivo-objeto para main.c e add.c
gcc -c -o main.o main.c
gcc -c -o add.o add.c

# Gerar o executável final
gcc -o main.exe main.o add.o

Neste ponto, sua solução funciona perfeitamente: há um arquivo separado para a declaração da sua função, que é incluída em outro arquivo caso seja necessário usar a função.

Se fosse, no entanto, necessário incluir o seu arquivo-header mais de uma vez em arquivos diferentes (incluindo outros headers), recomendo usar um include guard. O include guard é um #if do preprocessador usado para garantir que um arquivo não seja incluído mais de uma vez, e faz sentido apenas para arquivos-header. Alteramos apenas o add.h todo o resto continua igual:

Arquivo add.h:

#ifndef ADD_H_
#define ADD_H_

int add(int a, int b);

# endif

Uma solução mais simples, mas menos portável (e talvez com alguns poréns), é utilizar #pragma once:

Arquivo add.h:

#pragma once

int add(int a, int b);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment