Как обойти многократное определение имени функции при реализации общих структур данных с помощью макросов в C?

Я пишу код в среде, написанной на C и несовместимой с C++. При реализации структуры данных для наборов в виде двусвязных списков я использовал подобные макросы для создания дженериков.

#pragma once

/* Interface protocol:
 *   1. Do not modify contents of nodes in a list. Results should be regarded
 *      as returned instead of modified in the arguments. The old list shall
 *      not be used.
 *   2. Use RA_DECL_DL (T) to declare the doubly linked list and related oper
 *      for type T. If RA_DECL_DL (T) is used in an `.h' file, put it in
 *      #define GUARD_RA_FL_<T> #ifdef GUARD_RA_FL_<T> ... #endif
 *      to prevent re-declarations.
 *   3. A function int _<T>_eq (T x, T y) must be declared in that file, before
 *      RA_DECL_DL and RA_DEFN_DL.
 *   4. An RA_DL (T) can be used both as a set or a doubly linked list, but not
 *      simultaneously for a particular instance. */

#define RA_DECL_DL(T)                                                         \
  typedef struct _##T##_DL_ *T##_DL_t;                                        \
  struct _##T##_DL_                                                           \
  {                                                                           \
    T val;                                                                    \
    T##_DL_t next;                                                            \
    T##_DL_t prev;                                                            \
  };                                                                          \
  T##_DL_t _##T##_DL_empty ();                                                \
  T##_DL_t _##T##_DL_insert (T x, T##_DL_t lst);                              \
  T##_DL_t _##T##_DL_remove (T x, T##_DL_t lst);                              \
  int _##T##_DL_isIn (T x, T##_DL_t lst);                                     \
  int _##T##_DL_isEmpty (T##_DL_t lst);                                       \
  T##_DL_t _##T##_DL_union (T##_DL_t l1, T##_DL_t l2);                        \
  T##_DL_t _##T##_DL_intersect (T##_DL_t l1, T##_DL_t l2);                    \
  T##_DL_t _##T##_DL_diff (T##_DL_t l1, T##_DL_t l2);

#define RA_DEFN_DL(T)                                                         \
  T##_DL_t _##T##_DL_empty () { return NULL; }                                \
  T##_DL_t _##T##_DL_insert (T x, T##_DL_t lst)                               \
  {                                                                           \
    T##_DL_t ret = checked_malloc (sizeof (struct _##T##_DL_));               \
    if (lst == NULL)                                                          \
      {                                                                       \
        ret->val = x;                                                         \
        ret->next = NULL;                                                     \
        ret->prev = NULL;                                                     \
        return ret;                                                           \
      }                                                                       \
    else                                                                      \
      {                                                                       \
        for (T##_DL_t iter = lst; iter != NULL; iter = iter->next)            \
          {                                                                   \
            if (_##T##_eq (iter->val, x))                                     \
              return lst;                                                     \
          }                                                                   \
        ret->val = x;                                                         \
        ret->prev = NULL;                                                     \
        ret->next = lst;                                                      \
        lst->prev = ret;                                                      \
        return ret;                                                           \
      }                                                                       \
  }                                                                           \
  T##_DL_t _##T##_DL_remove (T x, T##_DL_t lst)                               \
  {                                                                           \
    T##_DL_t iter;                                                            \
    for (iter = lst; iter != NULL; iter = iter->next)                         \
      {                                                                       \
        if (_##T##_eq (iter->val, x))                                         \
          break;                                                              \
      }                                                                       \
    if (iter == NULL)                                                         \
      return lst;                                                             \
    if (iter->prev != NULL)                                                   \
      iter->prev->next = iter->next;                                          \
    if (iter->next != NULL)                                                   \
      iter->next->prev = iter->prev;                                          \
    return iter->prev == NULL ? lst->next : lst;                              \
  }                                                                           \
  int _##T##_DL_isIn (T x, T##_DL_t lst)                                      \
  {                                                                           \
    for (T##_DL_t iter = lst; iter != NULL; iter = iter->next)                \
      {                                                                       \
        if (_##T##_eq (iter->val, x))                                         \
          return 1;                                                           \
      }                                                                       \
    return 0;                                                                 \
  }                                                                           \
  T##_DL_t _##T##_DL_union (T##_DL_t l1, T##_DL_t l2)                         \
  {                                                                           \
    T##_DL_t ret = _##T##_DL_empty ();                                        \
    for (T##_DL_t iter = l1; iter != NULL; iter = iter->next)                 \
      ret = _##T##_DL_insert (iter->val, ret);                                \
    for (T##_DL_t iter = l2; iter != NULL; iter = iter->next)                 \
      ret = _##T##_DL_insert (iter->val, ret);                                \
    return ret;                                                               \
  }                                                                           \
  T##_DL_t _##T##_DL_intersect (T##_DL_t l1, T##_DL_t l2)                     \
  {                                                                           \
    T##_DL_t ret = _##T##_DL_empty ();                                        \
    for (T##_DL_t iter = l1; iter != NULL; iter = iter->next)                 \
      {                                                                       \
        if (_##T##_DL_isIn (iter->val, l2))                                   \
          ret = _##T##_DL_insert (iter->val, ret);                            \
      }                                                                       \
    return ret;                                                               \
  }                                                                           \
  T##_DL_t _##T##_DL_diff (T##_DL_t l1, T##_DL_t l2)                          \
  {                                                                           \
    T##_DL_t ret = _##T##_DL_empty ();                                        \
    for (T##_DL_t iter = l1; iter != NULL; iter = iter->next)                 \
      {                                                                       \
        if (!_##T##_DL_isIn (iter->val, l2))                                  \
          ret = _##T##_DL_insert (iter->val, ret);                            \
      }                                                                       \
    return ret;                                                               \
  }

#define RA_DL(T) T##_DL_t
#define RA_DL_empty(T) _##T##_DL_empty ()

#define RA_DL_insert(T, x, lst) _##T##_DL_insert (x, lst)
#define RA_DL_remove(T, x, lst) _##T##_DL_remove (x, lst)
#define RA_DL_isIn(T, x, lst) _##T##_DL_isIn (x, lst)
#define RA_DL_union(T, l1, l2) _##T##_DL_union (l1, l2)
#define RA_DL_intersect(T, l1, l2) _##T##_DL_intersect (l1, l2)
#define RA_DL_diff(T, l1, l2) _##T##_DL_diff (l1, l2)

Проблема в следующем: если для двух файлов, скажем f1.c и f2.c, я пишу RA_DECL_DL(int) и RA_DEFN_DL(int) в обоих файлах, то, например, функция _int_DL_insert определена дважды глобально. Это вызывает конфликт между идентификаторами, хотя на самом деле они идентичны.

Использование защитных макросов #ifdef не решает эту проблему, поскольку макросы не видны в файлах .c. Использование static не позволяет мне использовать функции в нескольких файлах, что иногда необходимо.

Все это очень плохая практика... но есть лучший (менее плохой) способ сделать это с помощью X-макросов, если вас устраивает хранение централизованного списка всех разрешенных типов шаблонов в проекте (?). Вам придется добавлять/удалять типы в этот список вручную, но оттуда он может автоматически генерировать все остальное и без коллизий, поскольку для каждого типа будет ровно 1 объявление и 1 определение. Будет ли это работать или должно работать аналогично шаблонам C++?

Lundin 27.05.2024 11:39

Какие наборы инструментов вы должны поддерживать или требуется полностью переносимый C?

Mike Kinghan 27.05.2024 12:43

@MikeKinghan Полностью портативный и стандартный C, не более поздняя версия, чем C99.

Kagura Hitoha 27.05.2024 13:16

Используйте макрос RA_DECL_DL там, где вам нужны видимые функции. Используйте макрос RA_DEFN_DL в одном файле для каждого типа. Правила такие же, как если бы вы написали код без макросов. Будьте осторожны при использовании имен, начинающихся с подчеркивания.

Jonathan Leffler 27.05.2024 14:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я согласен с @Lundin, что это слишком много макросов. Я уверен, что вы знаете, что общая реализация с использованием шаблонов C++ проста — контейнер шаблонов. наберите std::set готов - но вы говорите свой Фреймворк «несовместим» с C++. Однако C++, создающий экземпляр шаблона реализация может предоставить интерфейс связи C с помощью extern C {....} спецификатор языковой связи , и вы Чтобы обеспечить безопасность ABI, нужно только уметь использовать одну и ту же цепочку инструментов для C и C++.

Если связь C++ с C по какой-то причине исключена, предположим, что ваш заголовочный файл макроса — DL_macros.h.

Если вы собираетесь контролировать инвентарь типов, для которых предназначены ваши макросы экземпляр, то вы можете сделать следующее.

Если вы хотите, чтобы пользователь ваших макросов мог использовать их в непредвиденных типы, то вы можете указать README, который подскажет им сделать следующее.

Для каждого типа Type, для которого вы хотите создать экземпляр макроса, создайте заголовочный файл с подходящим именем, DL_Type.h например:

#pragma once
#include <Type.h> // Maybe
#include <DL_macros.h>

int _Type_eq (Type x, Type y);  // Per your protocol item #3

RA_DECL_DL(Type)

где Type.h будет типом определения соответствующего заголовочного файла Type, если это определенный, а не фундаментальный тип.

Также создайте исходный файл DL_Type.c, например:

#include <DL_Type.h>

int _Type_eq (Type x, Type y)
{
    return x == y; // Or whatever is right.
}

RA_DEFN_DL(Type)

Затем скомпилируйте файл DL_Type.c в объектный файл DL_Type.o. Свяжите этот объектный файл с клиентским кодом, которому требуется API, объявленный в DL_Type.h. Такой код будет #include <DL_Type.h>. Клиентский код будет ссылаться на уникальные определения, представленные в DL_Type.o.

При желании вы или ваши пользователи можете создать статическую библиотеку из объектных файлов. создание экземпляров определений макросов для различных типов. Из такой статической библиотеки компоновщик будет связывать только объектные файлы-члены, на которые ссылается клиентский код.

Вы также можете избежать предоставления макроса RA_DEFN_DL(T) клиентскому коду, поместив макрос RA_DECL_DL(T) в заголовке, обращенном к клиенту, скажем, DL_macros_decl.h, поместив RA_DEFN_DL(T) в другой, скажем, DL_macros_defn.h и включив последний только в DL_Type.c:

#include <DL_Type.h>
#include <DL_macros_defn.h>

При желании вы также можете предоставить условную поддержку для цепочек инструментов, таких как GCC, которые распознают слабые символы. Компоновщик будет терпеть несколько определений слабого символа и произвольно выбирать одно из них (на практике первый увиденный), поэтому для этих цепочек инструментов это будет ненужно ни вам, ни вашим пользователям. скомпилировать и связать отдельный DL_Type.o для каждого Type: вы могли бы RA_DECL_DL(Type) и RA_DEFN_DL(Type) в той же единице перевода, как вы изначально предполагали, если думаю, что это очень желательно.

Для этого варианта DL_macros.h будет изменен в строках:

#ifdef __GNUC__
#define WEAK __attribute__((weak))
#else
#define WEAK
#endif

#define RA_DECL_DL(T)                                                         \
  typedef struct _##T##_DL_ *T##_DL_t;                                        \
  struct _##T##_DL_                                                           \
  {                                                                           \
    T val;                                                                    \
    T##_DL_t next;                                                            \
    T##_DL_t prev;                                                            \
  };                                                                          \
  T##_DL_t _##T##_DL_empty () WEAK;                                           \
  T##_DL_t _##T##_DL_insert (T x, T##_DL_t lst) WEAK;                         \
  T##_DL_t _##T##_DL_remove (T x, T##_DL_t lst) WEAK;                         \
  int _##T##_DL_isIn (T x, T##_DL_t lst) WEAK;                                \
  int _##T##_DL_isEmpty (T##_DL_t lst) WEAK;                                  \
  T##_DL_t _##T##_DL_union (T##_DL_t l1, T##_DL_t l2) WEAK;                   \
  T##_DL_t _##T##_DL_intersect (T##_DL_t l1, T##_DL_t l2) WEAK;               \
  T##_DL_t _##T##_DL_diff (T##_DL_t l1, T##_DL_t l2) WEAK;
  

и требуемое объявление для функции _Type_eq будет иметь вид

int _Type_eq (Type x, Type y) WEAK;

Я бы предпочел не заморачиваться с дифференциацией цепочек инструментов. которые распознают слабые символы и повсеместно придерживаются составления четкого DL_Type.o.

Предостережение @Jonathan Leffler по поводу использования имен, начинающихся с подчеркивания относится к тому факту, что имена, начинающиеся с двойного подчеркивания или символа подчеркивание и заглавная буква зарезервированы разработчиком компилятора в C. Ваши макросы могут давать имена которые нарушают эту оговорку.

@KaguraHitoha P.S. Если вы исследуете extern C {...} связывание в C++ для своего решения, вам нужно будет научиться явно создавать экземпляр шаблона функции. Посмотрите это и это

Mike Kinghan 28.05.2024 10:33

Другие вопросы по теме