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

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

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

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

78 голосов
/ 25 сентября 2008

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

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

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

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

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

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

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

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

#define DEBUG_ENABLED true

вместо

#define DEBUG_ENABLED (1)
55 голосов
/ 03 июля 2010

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

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

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

29 голосов
/ 26 сентября 2008

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

#define FOO 0

тогда

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
18 голосов
/ 01 июля 2010

У нас была одна и та же проблема в нескольких файлах, и всегда есть проблема с людьми, которые забыли включить файл «флаг функции» (с кодовой базой> 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

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

15 голосов
/ 26 сентября 2008

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

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

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

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

14 голосов
/ 25 сентября 2008

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

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

7 голосов
/ 25 сентября 2008

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

#if defined(DEBUG_ENABLED)

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

#if !defined(DEBUG_ENABLED)

против

#ifndef(DEBUG_ENABLED)
6 голосов
/ 25 сентября 2008

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

#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);
4 голосов
/ 25 сентября 2008

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

3 голосов
/ 25 сентября 2008

# 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 ++: вызов функции прошел успешно. В этом простом случае это забавно. Но мой собственный опыт работы с макросами в режиме беззвучного воспроизведения моего кода не полон радости и полной реализации, так что ...

# 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 ++.

Post Scriptum

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

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

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

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

Интересно. : -)

...