Почему #ifdef нужно избежать в.c файлах?

Я в настоящее время работаю над phpDataMapper, который является ORM, разработанным для имения простого синтаксиса как проект Ruby Datamapper. Это находится все еще в ранней разработке также, но это работает отлично.

31
задан Norman Ramsey 5 December 2009 в 05:06
поделиться

10 ответов

Hard to maintain. Better use interfaces to abstract platform specific code than abusing conditional compilation by scattering #ifdefs all over your implementation.

E.g.

void foo() {
#ifdef WIN32
   // do Windows stuff
#else
   // do Posix stuff
#endif
   // do general stuff
}

Is not nice. Instead have files foo_w32.c and foo_psx.c with

foo_w32.c:

void foo() {
    // windows implementation
}

foo_psx.c:

void foo() {
    // posix implementation
}

foo.h:

void foo();  // common interface

Then have 2 makefiles1: Makefile.win, Makefile.psx, with each compiling the appropriate .c file and linking against the right object.

Minor amendment:

If foo()'s implementation depends on some code that appears in all platforms, E.g. common_stuff()2, simply call that in your foo() implementations.

E.g.

common.h:

void common_stuff();  // May be implemented in common.c, or maybe has multiple
                      // implementations in common_{A, B, ...} for platforms
                      // { A, B, ... }. Irrelevant.

foo_{w32, psx}.c:

void foo()  {  // Win32/Posix implementation
   // Stuff
   ...
   if (bar) {
     common_stuff();
   }
}

While you may be repeating a function call to common_stuff(), you can't parameterize your definition of foo() per platform unless it follows a very specific pattern. Generally, platform differences require completely different implementations and don't follow such patterns.


  1. Makefiles are used here illustratively. Your build system may not use make at all, such as if you use Visual Studio, CMake, Scons, etc.
  2. Even if common_stuff() actually has multiple implementations, varying per platform.
55
ответ дан 27 November 2019 в 21:53
поделиться

(В некоторой степени не соответствует заданному вопросу )

Однажды я увидел совет, предлагающий использовать блоки #if (n) def / # endif для использования в отладке / изолировании кода вместо комментирования.

Это было предложено, чтобы помочь избежать ситуаций в котором комментируемый раздел уже содержит комментарии к документации, и должно быть реализовано решение вроде следующего:

/* <-- begin debug cmnt   if (condition) /* comment */
/* <-- restart debug cmnt {
                              ....
                          }
*/ <-- end debug cmnt

Вместо этого это будет:

#ifdef IS_DEBUGGED_SECTION_X

                if (condition) /* comment */
                {
                    ....
                }
#endif

Мне показалось отличной идеей. Хотел бы я запомнить источник и связать его: (

6
ответ дан 27 November 2019 в 21:53
поделиться
  1. Because then when you do search results you don't know if the code is in or out without reading it.

  2. Because they should be used for OS/Platform dependencies, and therefore that kind of code should be in files like io_win.c or io_macos.c

3
ответ дан 27 November 2019 в 21:53
поделиться

Моя интерпретация этого правила: На логику вашей (алгоритмической) программы не должны влиять определения препроцессора. Работа вашего кода всегда должна быть лаконичной. Любая другая форма логики (платформа, отладка) должна абстрагироваться в файлах заголовков.

Это скорее руководство, чем строгое правило, ИМХО. Но я согласен с тем, что решения на основе c-синтаксиса предпочтительнее препроцессорной магии.

3
ответ дан 27 November 2019 в 21:53
поделиться

Разумная цель, но не настолько велика, как строгое правило.

Совет сохранять условные выражения препроцессора в файлах заголовков - это хорошо, поскольку позволяет выбирать интерфейсы условно, но не засорять код запутанной и уродливой логикой препроцессора. .

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

#if defined(SOME_IOCTL)
   case SOME_IOCTL:
   ...
#endif
#if defined(SOME_OTHER_IOCTL)
   case SOME_OTHER_IOCTL:
   ...
#endif
#if defined(YET_ANOTHER_IOCTL)
   case YET_ANOTHER_IOCTL:
   ...
#endif
2
ответ дан 27 November 2019 в 21:53
поделиться

CPP - это отдельный (неполный по Тьюрингу) макроязык поверх (обычно) C или C ++. Таким образом, его легко перепутать с базовым языком, если вы не будете осторожны. Во всяком случае, это обычный аргумент против макросов, а не, например, шаблонов C ++. Но #ifdef? Просто попробуйте прочитать чей-то код, который вы никогда раньше не видели, в котором есть куча ifdef.

например, попробуйте прочитать эти функции Рида-Соломона умножить блок на постоянное значение Галуа: http://parchive.cvs.sourceforge.net/viewvc/parchive/par2-cmdline/reedsolomon.cpp?revision=1.3&view=markup

Если у вас не было следующей подсказки, вам будет предложено минута, чтобы понять, что происходит: есть две версии: одна простая, а другая с предварительно вычисленной таблицей поиска (LONGMULTIPLY). Тем не менее, получайте удовольствие, отслеживая #if BYTE_ORDER == __LITTLE_ENDIAN. Мне стало намного легче читать, когда я переписал этот бит для использования функции le16_to_cpu (определение которой было внутри предложений #if), вдохновленных материалом Linux byteorder.h.

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

1
ответ дан 27 November 2019 в 21:53
поделиться

Если ваш код будет скомпилирован с разными компиляторами C, и вы используете специфичные для компилятора функции, то вам может потребоваться определить, какие предопределенные макросы доступны.

0
ответ дан 27 November 2019 в 21:53
поделиться

Обязательно отдавайте предпочтение абстракции над условной компиляцией. Однако любой, кто написал портативное программное обеспечение, может сказать вам, что количество изменений среды ошеломляет. Некоторая дизайнерская дисциплина может помочь, но иногда выбор стоит между элегантностью и соблюдением графика. В таких случаях может потребоваться компромисс.

0
ответ дан 27 November 2019 в 21:53
поделиться

Условную компиляцию трудно отладить. Чтобы понять, какой блок кода программа будет выполнять, нужно знать все настройки.

Однажды я потратил неделю на отладку многопоточного приложения, в котором использовалась условная компиляция. Проблема заключалась в том, что идентификатор был написан по-разному. Один модуль использовал #if FEATURE_1 , в то время как проблемная область использовала #if FEATURE1 (обратите внимание на подчеркивание).

Я большой сторонник того, чтобы make-файл обрабатывал конфигурацию, включая правильные библиотеки или объекты. Делает код более читаемым. Кроме того, большая часть кода становится независимой от конфигурации, и только несколько файлов зависят от конфигурации.

2
ответ дан 27 November 2019 в 21:53
поделиться

Это правда, что #if #endif действительно усложняет чтение кода. Однако я видел много реального кода, у которого нет проблем с его использованием, и который все еще работает. Так что могут быть более эффективные способы избежать использования #if #endif, но их использование не так уж и плохо при надлежащей осторожности.

1
ответ дан 27 November 2019 в 21:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: