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

112

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

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

//---- 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
}

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

Джон Кейдж
источник
Примечание: с #if, вы также можете использовать #elifпоследовательно, в отличие от с #ifdef. Таким образом, вместо того, чтобы просто использовать #define BLAH, использовать #define BLAH 1с #if BLAHи т. Д.
Эндрю

Ответы:

82

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

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

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

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

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

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

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

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

#define DEBUG_ENABLED true

вместо того

#define DEBUG_ENABLED (1)
Родди
источник
Константа может не использоваться для включения / отключения отладки, поэтому запуск #ifdef с #define равным 0 может быть не таким уж безобидным. Что касается true / false, они были добавлены в C99 и отсутствуют в C89 / C90.
Майкл Карман,
Майкл: Он / она выступали против использования #ifdef ?!
Джон Кейдж,
7
Да, одна проблема в #ifdefтом, что он работает с вещами, которые не определены; не определены ли они намеренно, из-за опечатки или чего-то еще.
bames53
6
Ваше добавление к ответу неверно. #if DEBUG_ENBALEDне является ошибкой, обнаруженной препроцессором. Если DEBUG_ENBALEDне определен, он расширяется до токена 0в #ifдирективах.
R .. GitHub НЕ ПОМОГАЕТ ICE
6
@R .. Во многих компиляторах вы можете включить предупреждение для "#if DEBUG_ENABLED", когда DEBUG_ENABLED не определен. В GCC используйте "-Wundef". В Microsoft Visual Studio используйте «/ w14668», чтобы включить C4668 как предупреждение уровня 1.
Will
56

Они оба ужасны. Вместо этого сделайте следующее:

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

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

R .. GitHub НЕ ПОМОГАЕТ ICE
источник
6
@MatthieuM. На самом деле, я думаю, что оригинальная версия была в порядке. Точка с запятой будет интерпретироваться как пустой оператор. Однако если забыть точку с запятой, это может быть опасно.
Кейси Кубалл
31

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

#define FOO 0

затем

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
Теренс Симпсон
источник
20

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

Если у вас был feature.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

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

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

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

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

если вы изменили вышеуказанный feature.h на:

#ifndef FEATURE_H
#define FEATURE_H

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

#endif // FEATURE_H

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

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

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

Брент Придди
источник
17

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

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

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

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

Майк Томпсон
источник
1
в #if definedчем definedэто ключевое слово или?
nmxprime 02
15

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

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

Дерек Парк
источник
8

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

#if defined(DEBUG_ENABLED)

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

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)
Джим Бак
источник
8
Лично я считаю, что этот восклицательный знак проще пропустить!
Джон Кейдж,
6
С подсветкой синтаксиса? :) При подсветке синтаксиса «n» в «ifndef» заметить гораздо труднее, поскольку все они одного цвета.
Джим Бак,
Хорошо, я имел в виду, что #ifndef легче обнаружить, чем #if! Defined, когда вы сравниваете с #if определенным ... но учитывая все #if defined / # if! Defined vs # ifdef / # ifndef, любой из них одинаково плохо читается!
Джон Кейдж,
@JonCage Я знаю, что прошло несколько лет с момента этого комментария, но я хотел бы отметить, что вы можете написать его, #if ! definedчтобы сделать !более заметным и трудно пропустить.
Pharap,
@Pharap - Это определенно похоже на улучшение :)
Джон Кейдж
7

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

#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);
лев
источник
3
Возможно, макрос печати был не лучшим примером - на самом деле мы уже делаем это в нашей кодовой базе для нашего более стандартного кода отладки. Мы используем биты #if / #ifdefined для областей, в которых вы, возможно, захотите включить дополнительную отладку ..
Джон Кейдж,
5

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

Мартин Беккет
источник
1
Это не удается, поскольку #define DEBUG = 0 теперь не будет запускаться #if, но будет запускаться
#ifdef
1
В том-то и дело, что я могу либо полностью удалить DEBUG, либо просто установить для него значение 0, чтобы отключить его.
Мартин Беккет,
так и должно быть #define DEBUG 1. Not#define DEBUG=1
Кешава GN
4

#if и #define MY_MACRO (0)

Использование #if означает, что вы создали макрос «определить», т. Е. Что-то, что будет искать в коде и заменить на «(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 ++: вызов функции был успешным. В этом простом случае это забавно. Но мой собственный опыт с макросами, незаметно играющими с моим кодом, не полон радости и наполнения, так что ...

#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 (или даже #if, определенных как было предложено Джимом Баком), и, прежде всего, присвоение им таких длинных и чужеродных имен, которые никто в здравом уме не будет использовать. это «случайно», и это никоим образом не повлияет на законный код C ++.

Пост скриптум

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

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

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

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

Интересный. :-)

paercebal
источник
Я согласен с тем, что макросы могут быть опасными, но этот первый пример будет довольно очевиден для отладки и, конечно, дает только одну ошибку. Почему вы ожидаете большего? Я видел гораздо более отвратительные ошибки, связанные с макросами ...
Джон Кейдж,
Это правда, что разница между одним решением и другим почти тривиальна. Но в этом случае, поскольку мы говорим о двух конкурирующих стилях кодирования, то даже тривиальное нельзя игнорировать, потому что после этого все, что остается, - это личный вкус (и в этот момент я считаю, что его не следует нормализовать. )
paercebal
4

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

#ifdef 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 

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

tmanthey
источник
2

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

Axblount
источник
2

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

Зврба
источник
1
OP не спрашивал, что лучше между "#ifdef" и "#if defined", а скорее между "# ifdef / # if defined" и "#if".
Shank
1

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

Дэвид Нехме
источник
1
Я согласен, и мы действительно делаем это в нашем коде, мне просто нужен пример того, для чего вы могли бы использовать #if и т. Д.
Джон Кейдж,
1

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

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

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

StefanB
источник
0

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

tloach
источник
По какой-то конкретной причине (из любопытства)?
Джон Кейдж,
2
Честно говоря, я никогда об этом не думал - просто как это делалось там, где я работал. Это дает то преимущество, что вместо того, чтобы вносить изменения в код для производственной сборки, все, что вам нужно сделать, это «сделать DEBUG» для отладки или «сделать ПРОИЗВОДСТВО» для обычного
tloach
0

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

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

Дима
источник
1
Это паршивое решение, у него есть следующие проблемы: 1. Работает только в функциях, вы не можете удалить ненужные переменные класса и т. Д. 2. Компиляторы могут выдавать предупреждения о недоступности кода 3. Код в if все еще требует компиляции, что означает вы должны сохранить все свои функции отладки и т. д.
Дон Нойфельд,
Сначала вопрос был конкретно об отладке printfs, поэтому ненужные переменные класса здесь не проблема. Во-вторых, учитывая возможности современных компиляторов, вам следует как можно меньше использовать #ifdefs. В большинстве случаев вместо этого вы можете использовать конфигурации сборки или специализации шаблонов.
Дима
0

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

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

вывод:

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

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

Томилов Анатолий
источник
0

Мне нравится, #define DEBUG_ENABLED (0)когда вам может понадобиться несколько уровней отладки. Например:

#define DEBUG_RELEASE (0)
#define DEBUG_ERROR (1)
#define DEBUG_WARN (2)
#define DEBUG_MEM (3)
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL (DEBUG_RELEASE)
#endif
//...

//now not only
#if (DEBUG_LEVEL)
//...
#endif

//but also
#if (DEBUG_LEVEL >= DEBUG_MEM)
LOG("malloc'd %d bytes at %s:%d\n", size, __FILE__, __LINE__);
#endif

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

Кроме того, #ifndefвокруг определения упрощается выбор определенного уровня отладки в командной строке:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

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

мемта
источник