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

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

Примечание: with #if, you can also then use #elif in a consistent manner, unlike with #ifdef. Thus, instead of just using #define BLAH, use #define BLAH 1 with #if BLAH, etc...
Andrew 29.05.2018 02:51
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
120
1
99 803
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

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

Это не удается, поскольку #define DEBUG = 0 теперь не будет запускаться #if, но будет запускаться #ifdef

tloach 25.09.2008 22:33

В том-то и дело, я могу либо полностью удалить DEBUG, либо просто установить для него значение 0, чтобы отключить его.

Martin Beckett 26.09.2008 04:55

это должен быть #define DEBUG 1. Не #define DEBUG=1

Keshava GN 22.04.2013 15:40

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

#if defined(DEBUG_ENABLED)

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

#if !defined(DEBUG_ENABLED)

против.

#ifndef(DEBUG_ENABLED)

Лично я считаю, что этот восклицательный знак проще пропустить!

Jon Cage 26.09.2008 11:41

С подсветкой синтаксиса? :) При выделении синтаксиса «n» в «ifndef» заметить гораздо труднее, поскольку все они одного цвета.

Jim Buck 26.09.2008 21:11

Хорошо, я имел в виду, что #ifndef легче обнаружить, чем #if! Defined, когда вы сравниваете с #if определенным ... но учитывая все #if defined / # if! Defined vs # ifdef / # ifndef, любой из них одинаково плохо читается!

Jon Cage 27.09.2008 03:22

@JonCage Я знаю, что прошло несколько лет с момента этого комментария, но я хотел бы отметить, что вы можете написать его как #if ! defined, чтобы сделать ! более заметным и его трудно пропустить.

Pharap 14.03.2016 07:54

@Pharap - Это определенно похоже на улучшение :)

Jon Cage 16.03.2016 15:13

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

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

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

Это паршивое решение, оно имеет следующие проблемы: 1. Работает только в функциях, вы не можете удалить ненужные переменные класса и т. д. 2. Компиляторы могут выдавать предупреждения о недоступности кода 3. Код в if все еще нуждается в компиляции, что означает вы должны сохранить все свои функции отладки и т. д.

Don Neufeld 25.09.2008 22:36

Сначала вопрос был конкретно об отладке printfs, поэтому ненужные переменные класса здесь не проблема. Во-вторых, учитывая возможности современных компиляторов, вам следует как можно меньше использовать #ifdefs. В большинстве случаев вместо этого вы можете использовать конфигурации сборки или специализации шаблонов.

Dima 25.09.2008 22:42

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

По какой-то конкретной причине (из любопытства)?

Jon Cage 26.09.2008 11:44

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

tloach 26.09.2008 18:09

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

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

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

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

shank 24.06.2010 19:35

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

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

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

Jon Cage 26.09.2008 11:37

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

paercebal 27.09.2008 23:11

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

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

Jon Cage 26.09.2008 11:33
Ответ принят как подходящий

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

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

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

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

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

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

И, в-третьих, вы говорите, что в вашей команде раскол. Я предполагаю, что это означает, что разные участники уже приняли разные подходы, и вам необходимо стандартизировать их. Решение о том, что #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.

Michael Carman 26.09.2008 03:03

Майкл: Он / она защищал против использование #ifdef ?!

Jon Cage 26.09.2008 11:48

... хороший момент относительно истинного / ложного - тем более, что наша встроенная платформа на самом деле не определяет bool!

Jon Cage 26.09.2008 11:49

@ Джон Кейдж - какая платформа? Некоторые из них есть, но в странных местах.

detly 03.07.2010 22:10

Да, одна проблема с #ifdef заключается в том, что он работает с вещами, которые не определены; не определены ли они намеренно, из-за опечатки или чего-то еще.

bames53 11.06.2013 01:19

Ваше добавление к ответу неверно. #if DEBUG_ENBALED не является ошибкой, обнаруженной препроцессором. Если DEBUG_ENBALED не определен, он заменяется маркером 0 в директивах #if.

R.. GitHub STOP HELPING ICE 12.07.2013 06:24

@R .. Во многих компиляторах вы можете включить предупреждение для "#if DEBUG_ENABLED", когда DEBUG_ENABLED не определен. В GCC используйте "-Wundef". В Microsoft Visual Studio используйте «/ w14668», чтобы включить C4668 как предупреждение уровня 1.

Will 18.01.2014 03:26

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

#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 / #ifdefined для областей, в которых вы, возможно, захотите включить дополнительную отладку.

Jon Cage 26.09.2008 11:43

В целях выполнения условной компиляции #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 что такое defined это ключевое слово или?

nmxprime 02.04.2014 14:16

#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

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

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

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

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

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

Casey Kuball 10.09.2014 20:06

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

#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 

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

Раньше я использовал #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.

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

Другие вопросы по теме