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