Подтвердить что ты не робот

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

Программист, которого я уважаю, сказал, что в коде C #if и #ifdef следует избегать любой ценой, за исключением, возможно, файлов заголовков. Почему было бы плохой практикой программирования использовать #ifdef в .c файле?

4b9b3361

Ответ 1

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

например.

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

Не приятно. Вместо этого есть файлы foo_w32.c и foo_psx.c с

foo_w32.c:

void foo() {
    // windows implementation
}

foo_psx.c:

void foo() {
    // posix implementation
}

foo.h:

void foo();  // common interface

Затем у вас есть 2 make файла 1: Makefile.win, Makefile.psx, каждый из которых компилирует соответствующий файл .c и связывается с правильным объектом.

Малая поправка:

Если реализация foo() зависит от кода, который появляется на всех платформах, например. common_stuff() 2 просто назовите это в своих реализациях foo().

например.

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();
   }
}

В то время как вы можете повторять вызов функции common_stuff(), вы не можете параметризовать свое определение foo() на платформу, если оно не соответствует очень конкретному шаблону. Как правило, различия в платформе требуют совершенно разных реализаций и не следуют таким шаблонам.


  • Makefile используются здесь иллюстративно. Ваша система сборки может вообще не использовать make, например, если вы используете Visual Studio, CMake, Scons и т.д.
  • Даже если common_stuff() фактически имеет несколько реализаций, изменяющихся для каждой платформы.

Ответ 2

(Несколько от заданного вопроса)

Я видел подсказку, предлагая использовать блоки #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

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

Ответ 3

  • Потому что тогда, когда вы выполняете поиск, вы не знаете, находится ли код или нет, не читая его.

  • Потому что они должны использоваться для зависимостей ОС/Платформы, и поэтому такой код должен быть в таких файлах, как io_win.c или io_macos.c

Ответ 4

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

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

Ответ 5

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

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

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

#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

Ответ 6

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

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

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

Ответ 7

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

например. попробуйте прочитать эти функции умножения-повторения на основе значения Рида-Соломона-по-константе-Галуа: 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), вдохновленное файлом byteorder.h Linux.

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

Ответ 11

Поздний ответ, но я наткнулся на это, ища что-то связанное.

Рассмотрим ситуацию, когда вам необходимо предоставить полностью протестированный код, со 100% -ным охватом веток и т.д. Теперь добавьте условную компиляцию.

Каждый уникальный символ, используемый для управления условной компиляцией, удваивает количество вариантов кода, которые необходимо проверить. Итак, один символ - у вас есть два варианта. Два символа, теперь у вас есть четыре разных способа скомпилировать ваш код. И так далее.

И это применимо только для булевых тестов, таких как #ifdef. Вы можете легко представить себе проблему, если тест имеет форму #if VARIABLE == SCALAR_VALUE_FROM_A_RANGE.