Tenho um arquivo de cabeçalho que apresenta uma macro que declara uma estrutura Stack e algumas funções associadas que funcionam com essa estrutura. O conteúdo do arquivo de cabeçalho (Stack.h) é mostrado abaixo:
#ifndef STACK_H
#define STACK_H
#include <stddef.h> // size_t
#include <malloc.h> // malloc(), calloc()
#define STACK_DEFAULT_GROWTH_FACTOR 2.0
#define STACK_REALLOC_FAIL -1
#define DECL_STACK(T, Id) \
typedef struct Stack_##Id { \
size_t capacity; \
size_t size; \
float growth_factor; \
T *data; \
} Stack_##Id; \
\
Stack_##Id* Stack_##Id##_New(size_t capacity) { \
Stack_##Id *p = malloc(sizeof(Stack_##Id)); \
if (!p) return NULL; \
T *d = calloc(capacity, sizeof(T)); \
if (!d) { \
free(p); \
return NULL; \
} \
p->capacity = capacity; \
p->size = 0; \
p->growth_factor = STACK_DEFAULT_GROWTH_FACTOR; \
p->data = d; \
return p; \
} \
\
void Stack_##Id##_Free(Stack_##Id *stack) { \
free(stack->data); \
free(stack); \
} \
\
void Stack_##Id##_Push(Stack_##Id *stack, const T item) { \
if (stack->size >= stack->capacity) { \
size_t new_capacity = (size_t)(stack->capacity * stack->growth_factor); \
T *d = realloc(stack->data, new_capacity); \
if (!d) { \
free(stack); \
exit(STACK_REALLOC_FAIL); \
return; \
} \
stack->capacity = new_capacity; \
} \
stack->data[stack->size++] = item; \
} \
\
T Stack_##Id##_Pop(Stack_##Id *stack) { \
return stack->data[--(stack->size)]; \
} \
\
T Stack_##Id##_Peek(const Stack_##Id *stack) { \
return stack->data[stack->size - 1]; \
}
#endif // STACK_H
Tenho um arquivo Main.c que inclui Stack.h e outro arquivo de cabeçalho chamado Some.h. Some.h também inclui Stack.h para usar a macro mencionada para declarar uma função que utiliza a estrutura de pilha declarada de alguma forma. O arquivo Some.c fornece a definição da função de forma mais simples. Eu compilo e tento vincular o programa ao seguinte makefile:
objects = Main.o Some.o
CC = gcc
flags = -Wall -Wextra
Some.o : Some.c Some.h Stack.h
$(CC) $(flags) -c Some.c Some.h Stack.h
Main.o : Main.c Some.h Stack.h
$(CC) $(flags) -c Main.c Some.h Stack.h
Main : $(objects)
$(CC) $(objects) -o Main
clean: Main
rm $(objects)
Aqui está o conteúdo de Main.c, Some.h e Some.c: Main.c:
#include <stdio.h>
#include "Stack.h"
#include "Some.h"
#ifndef STACK_INT
#define STACK_INT
DECL_STACK(int, int);
#endif
int main() {
Stack_int *stack = Stack_int_New(10);
Stack_int_Push(stack, 100);
printf("%d\n", Stack_int_Peek(stack));
Stack_int_Pop(stack);
Use_stack(stack);
Stack_int_Free(stack);
return 0;
}
Alguns.h:
#include "Stack.h"
#include <stdio.h>
#ifndef STACK_INT
#define STACK_INT
DECL_STACK(int, int);
#endif
int Use_stack(Stack_int *stack);
Alguns.c:
#include "Some.h"
int Use_stack(Stack_int *stack) {
Stack_int_Push(stack, 1);
printf("%d\n", Stack_int_Peek(stack));
Stack_int_Push(stack, 2);
printf("%d\n", Stack_int_Peek(stack));
Stack_int_Pop(stack);
return Stack_int_Pop(stack);
}
Arquivos de objeto são compilados com sucesso, mas recebo um erro na etapa de vinculação que afirma que várias definições de função estão presentes para as funções Stack_int_New(), Stack_int_Free(), etc. Várias definições estão presentes apenas para funções, não para a própria struct Stack_int (o que me surpreende, na verdade). Eu esperava que as proteções de expansão de macro inseridas ao redor da macro DECL_STACK impedissem a expansão de múltiplas macros e, portanto, impediriam a redefinição da função (e da struct). Mas isso não aconteceu. Tentei remover os arquivos de cabeçalho das receitas de compilação no makefile, mas não ajudou. Com eles ou sem eles, o comando gcc Main.c/Some.c -E
mostra que o pré-processador insere a macro e a expande de qualquer maneira. Como posso impedir a expansão de múltiplas macros se eu quiser incluir meu arquivo Stack.h em vários arquivos .c e declarar a pilha com o mesmo Id, ou seja, a mesma struct e funções para ela, mas sem que isso resulte em erros de vinculação?
A proteção de inclusão STACK_H funciona apenas no nível da unidade de compilação. Portanto, você pode incluir stack.h direta ou indiretamente várias vezes no mesmo arquivo de origem sem incorrer em problemas causados por definições duplicadas.
Mas durante a expansão da macro, você cria funções como Stack_int_New() etc. em cada arquivo de origem que usa stack.h. Essas funções são definidas no namespace global do seu programa. Durante o tempo de vinculação, as definições globais duplicadas são detectadas e você recebe mensagens de erro.
As soluções possíveis dependem do seu caso de uso. Se você quiser criar e acessar uma pilha de some.c e uma separada de main.c, como sugerido por @chux, você pode declarar as funções como estáticas, ou seja,
Isso torna cada implementação dessa função local na unidade de compilação. Dessa forma, você terá várias cópias independentes das suas funções de pilha no programa.
Se você quiser compartilhar a mesma pilha entre módulos de objeto, você deve colocar a implementação em seu próprio módulo, provavelmente um para cada tipo de variável necessária.
Também me surpreenderia se fosse verdade. Mas, na verdade, a
DECL_STACK()
macro apresentada na pergunta definirá um tipo de estrutura e algumas funções associadas a cada uso. Ao usar essa macro, você obtém múltiplas definições de estrutura. Mas isso não é um problema quando as duplicatas aparecem em diferentes unidades de tradução (aproximadamente, em diferentes arquivos-fonte em C). Essa variação em múltiplas definições do mesmo tipo de dado é extremamente comum.Mas múltiplas definições da mesma função externa são uma questão diferente. C não define o comportamento de um programa no qual tais conflitos aparecem, e é comum que vinculadores rejeitem entradas nas quais tais definições múltiplas aparecem.
De forma alguma. As proteções de inclusão múltipla impedem que o conteúdo do cabeçalho seja processado mais de uma vez se o cabeçalho for
#include
processado várias vezes na mesma unidade de tradução. Mas o processamento desse conteúdo serve apenas para definir aDECL_STACK()
macro. Uma vez que essa macro é definida em uma determinada TU, as proteções não têm nada a ver com o que acontece quando ela é usada nessa TU. E as proteções de inclusão são, em qualquer caso, relevantes apenas dentro de uma única TU.Sim, na maioria das vezes. Mas observe bem que, em cada TU, o pré-processador define a macro como consequência do processamento direto ou indireto de uma
#include "Stack.h"
diretiva. Não há problema com isso. Por outro lado, ele expande essa macro como consequência do nome da macro ser usado no código-fonte:DECL_STACK(int, int);
(onde isso não é suprimido por diretivas de compilação condicional).Se você não quiser que a macro seja expandida várias vezes, então não a utilize várias vezes.
Mas, no seu caso, você quer que funções em TUs diferentes interoperem com os mesmos objetos de pilha, passados de uma para a outra. Isso requer definições compatíveis do tipo de dado nas duas TUs, declarações das funções de pilha apropriadas em ambas as TUs, mas definições das funções de pilha em apenas uma TU. O cabeçalho que você apresentou simplesmente não prevê isso, embora possa ser modificado para isso.
Se você quiser usar essa implementação de pilha em várias TUs, e especialmente se quiser fazer isso sem ter várias versões redundantes das funções de pilha em seu binário, você deve dividir a
DECL_STACK
macro em duas:DECL_STACK
, que define o tipo de estrutura e declara (apenas) protótipos para as funções de pilha, eDEFINE_STACK
, que expandeDECL_STACK()
e também fornece as definições de função.Então, para qualquer combinação de tipo e ID, você
DEFINE_STACK(type, ID)
usa exatamente uma TU eDECL_STACK(type, ID)
qualquer outra TU onde queira usá-los. O cabeçalho que suporta isso pode ser assim:Pilha.h