AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79561089
Accepted
Andrey
Andrey
Asked: 2025-04-08 11:04:09 +0800 CST2025-04-08 11:04:09 +0800 CST 2025-04-08 11:04:09 +0800 CST

Erro de vinculação de múltiplas definições na expansão de macro com proteções de expansão

  • 772

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

c
  • 2 2 respostas
  • 74 Views

2 respostas

  • Voted
  1. Best Answer
    Martin Fischer
    2025-04-08T13:24:31+08:002025-04-08T13:24:31+08:00

    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,

    static Stack_##Id* Stack_##Id##_New(size_t capacity)
    

    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.

    • 5
  2. John Bollinger
    2025-04-08T22:57:41+08:002025-04-08T22:57:41+08:00

    Várias definições estão presentes apenas para funções, não para a estrutura Stack_int em si (o que me surpreende, na verdade).

    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.

    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, impedissem a redefinição da função (e da estrutura).

    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 #includeprocessado várias vezes na mesma unidade de tradução. Mas o processamento desse conteúdo serve apenas para definir a DECL_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.

    Mas isso não aconteceu. Tentei remover os arquivos de cabeçalho das receitas de compilação no makefile, mas não adiantou. Com ou sem eles, o comando gcc Main.c/Some.c -E mostra que o pré-processador insere a macro e a expande mesmo assim.

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

    Como posso evitar a expansão de múltiplas macros se eu quiser incluir meu arquivo Stack.h em vários arquivos .c e declarar pilha com o mesmo Id, ou seja, mesma estrutura e funções para ela, mas sem resultar em erros de vinculação?

    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_STACKmacro em duas:

    • DECL_STACK, que define o tipo de estrutura e declara (apenas) protótipos para as funções de pilha, e
    • DEFINE_STACK, que expande DECL_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 e DECL_STACK(type, ID)qualquer outra TU onde queira usá-los. O cabeçalho que suporta isso pode ser assim:

    Pilha.h

    #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); \
      void Stack_##Id##_Free(Stack_##Id *stack); \
      void Stack_##Id##_Push(Stack_##Id *stack, const T item); \
      T Stack_##Id##_Pop(Stack_##Id *stack); \
      T Stack_##Id##_Peek(const Stack_##Id *stack);
    
    #define DEFINE_STACK(T, Id) \
      #DECL_STACK(T, 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
    
    • 4

relate perguntas

  • Multiplicação mais rápida que *

  • Usando uma macro para comprimento de string no especificador de formato scanf () em C

  • Como você pode definir o tipo de dados de #define para long double?

  • Ponteiros const incompatíveis

  • Mudança de cor não gradual no OpenGL

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve