#ifdef vs #if-что лучше / безопаснее в качестве метода включения / отключения компиляции определенных разделов кода?

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

в основном, у нас есть некоторые операторы отладки печати, которые мы отключаем во время нормальной разработки. Лично я предпочитаю делать следующее:

//---- SomeSourceFile.cpp ----

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

#if(DEBUG_ENABLED)
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

некоторые из команды предпочитают следующее:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

#ifdef DEBUG_ENABLED
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

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

18 ответов


моя первая реакция была #ifdef конечно, но я думаю #if на самом деле имеет некоторые существенные преимущества для этого-вот почему:

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

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... вместо...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

во-вторых, вы находитесь в лучшем положении, если хотите мигрировать из #define к глобальной константе. #defines обычно не одобряются большинством программистов на C++.

и, в-третьих, вы говорите, что у вас есть разделение в вашей команде. Я предполагаю, что это означает, что разные члены уже приняли разные подходы, и вам нужно стандартизировать. Решение о том, что #if является предпочтительным выбором означает, что код с помощью #ifdef будет компилироваться - и запускаться-даже когда DEBUG_ENABLED ложно. И это много легче отслеживать и удалять отладочный вывод, который производится, когда не должно быть, чем наоборот.

О, и небольшая точка читаемости. Вы должны иметь возможность использовать true / false, а не 0/1 в своем #define, и поскольку значение является одним лексическим маркером, это единственный раз, когда вам не нужны круглые скобки вокруг него.

#define DEBUG_ENABLED true

вместо

#define DEBUG_ENABLED (1)

Они оба отвратительные. Вместо этого:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

затем, когда вам нужен код отладки, поместите его внутрь D();. И ваша программа не загрязнена отвратительными лабиринтами #ifdef.


#ifdef просто проверяет, определен ли токен, учитывая

#define FOO 0

затем

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"

у нас была такая же проблема с несколькими файлами, и всегда есть проблема с людьми, забывшими включить файл "флаг функций" (с кодовой базой > 41,000 файлов это легко сделать).

если у вас имеется.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

но тогда вы забыли включить файл заголовка в файл.cpp:

#if COOL_FEATURE
    // definitely awesome stuff here...
#endif

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

мы приняли портативный способ исправления для этого случая, а также тестирования состояния функции: макросы функций.

если вы изменили выше функцию.ч:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE() 1

#endif // FEATURE_H

но затем вы снова забыли включить файл заголовка в файл.cpp:

#if COOL_FEATURE()
    // definitely awseome stuff here...
#endif

препроцессор ошибся бы из-за использования неопределенного макроса функции.


Я думаю, что это исключительно вопрос стиля. Ни один из них не имеет явного преимущества перед другим.

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


для выполнения условной компиляции #if и #ifdef являются почти то же, но не совсем. Если ваша условная компиляция зависит от двух символов, то #ifdef также не будет работать. Например, предположим, что у вас есть два условных символа компиляции, PRO_VERSION и TRIAL_VERSION, у вас может быть что-то вроде этого:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

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

Я работаю над кодом, который широко использует условную компиляцию, и у нас есть смесь #if & #ifdef. Мы склонны использовать #ifdef / #ifndef для простого случая и #if всякий раз, когда два или более символа оцениваются.


Я сам предпочитаю:

#if defined(DEBUG_ENABLED)

Так как это упрощает создание кода, который ищет противоположное условие, гораздо проще определить:

#if !defined(DEBUG_ENABLED)

и

#ifndef(DEBUG_ENABLED)

Это вопрос стиля. Но я рекомендую более краткий способ сделать это:

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif

debug_print("i=%d\n", i);

вы делаете это один раз, а затем всегда используете debug_print() для печати или ничего не делаете. (Да, это будет компилироваться в обоих случаях.) Таким образом, ваш код не будет искажен директивами препроцессора.

Если вы получаете предупреждение "выражение не имеет эффекта" и хотите избавиться от него, вот альтернатива:

void dummy(const char*, ...)
{}

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif

debug_print("i=%d\n", i);

#if дает вам возможность установить его в 0, чтобы отключить функциональность, все еще обнаруживая, что переключатель есть.
Лично я всегда #define DEBUG 1 поэтому я могу поймать его с помощью #if или #ifdef


#if и #define MY_MACRO (0)

использование #if означает, что вы создали макрос "define", то есть то, что будет искать в коде, который будет заменен на "(0)". Это" макро-ад", который я ненавижу видеть в C++, потому что он загрязняет код потенциальными модификациями кода.

например:

#define MY_MACRO (0)

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

дает следующую ошибку на g++:

main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|

только один ошибка.

это означает, что ваш макрос успешно взаимодействовал с вашим кодом C++: вызов функции был успешным. В этом простом случае, это забавно. Но мой собственный опыт с макросами, играющими молча с моим кодом, не полон радости и fullfilment, поэтому...

#ifdef и #define MY_MACRO

использование #ifdef означает, что вы" определяете " что-то. Не то, чтобы вы придавали ему ценность. Он по-прежнему загрязняет, но, по крайней мере, он будет "заменен ничем" и не будет рассматриваться кодом C++ как лагитимный оператор кода. Тот же код выше, с простым определением, это:

#define MY_MACRO

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

дает следующие предупреждения:

main.cpp||In function ‘int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function ‘int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|

так...

вывод

Я бы предпочел жить без макросов в моем коде, но по нескольким причинам (определение заголовков или макросов отладки) я не могу.

но, по крайней мере, мне нравится делать их наименее интерактивными с моим законным кодом C++. Это означает использование #define без значения, используя #ifdef и #ifndef (или даже #если определено как предложенный Джимом баком), и, прежде всего, давая им имена так долго и так чуждо никто в здравом уме не будет использовать его "случайно", и что никоим образом не повлияет на законный код c++.

Post Scriptum

теперь, когда я перечитываю свой пост, я задаюсь вопросом, не следует ли мне попытаться найти какое-то значение, которое никогда не будет правильным C++, чтобы добавить к моему определению. Что-то вроде

#define MY_MACRO @@@@@@@@@@@@@@@@@@

это может использоваться с #ifdef и #ifndef, но не позволяет компилировать код, если используется внутри функции... Я попробовал это успешно на g++, и это дало ошибку:

main.cpp|410|error: stray ‘@’ in program|

интересные. :-)


первое кажется мне более ясным. Кажется более естественным сделать его флагом по сравнению с определенным / не определенным.


оба точно эквивалентны. В идиоматическом использовании #ifdef используется только для проверки определенности (и того, что я бы использовал в вашем примере), тогда как #if используется в более сложных выражениях, таких как #if defined(A) && !определено(B).


немного OT, но включение / выключение ведения журнала с препроцессором определенно является неоптимальным в C++. Есть хорошие инструменты ведения журнала, такие как Apache log4cxx которые являются открытым исходным кодом и не ограничивают распространение приложения. Они также позволяют изменять уровни ведения журнала без перекомпиляции, имеют очень низкие накладные расходы, если вы отключите выход из системы, и дают вам возможность полностью отключить выход из системы в процессе производства.


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

#ifdef macro

означает: "если макрос определен" или "если макрос не существует". Значение macro здесь не имеет значения. Это может быть что угодно.

#if macro

если всегда сравнивать со значением. В приведенном выше примере это стандартное неявное сравнение:

#if macro !=0

пример использования # if

#if CFLAG_EDITION == 0
    return EDITION_FREE;
#elif CFLAG_EDITION == 1
    return EDITION_BASIC;
#else
    return EDITION_PRO;
#endif

теперь вы можете либо поместить определение CFLAG_EDITION либо в свой код

#define CFLAG_EDITION 1 

или вы можете установить макрос как флаг компилятора. Также посмотреть здесь.


Я всегда использовал # ifdef и флаги компилятора для его определения...


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

вот что C++ Gotchas Стивен К. Дьюхерст говорит об использовании #if.


я использовал #ifdef, но когда я переключился на Doxygen для документации, я обнаружил, что прокомментированные макросы не могут быть документированы (или, по крайней мере, Doxygen выдает предупреждение). Это означает, что я не могу документировать макросы переключения функций, которые в настоящее время не включены.

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

поэтому в этом случае лучше всегда определять макросы и использовать #if.


существует разница в случае другого способа указать условное определение водителю:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

выход:

344c344
< #define A 
---
> #define A 1

Это означает, что -DA - это синоним -DA=1 и если значение опущено, то это может привести к проблемам в случае #if A использование.