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

StackOverflow https://stackoverflow.com/questions/135069

Вопрос

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

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

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

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

Это было полезно?

Решение

Моя первая реакция была #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 файлов это легко сделать).

Если бы у вас был 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

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

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

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

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

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

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

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

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

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

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

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

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

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

#ifdef macro

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

#if macro

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

#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, но когда я переключился на Doxygen для документации, я обнаружил, что закомментированные макросы не могут быть документированы (или, по крайней мере, Doxygen выдает предупреждение).Это означает, что я не могу документировать макросы переключения функций, которые в данный момент не включены.

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

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

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

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

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

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

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

выход:

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top