#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глобальної константи. #defines, як правило, нахмуриться більшість програмістів на C ++.

І, по-третє, ви кажете, що ви розділили свою команду. Думаю, це означає, що різні члени вже прийняли різні підходи, і вам потрібно стандартизувати. Постановка, що #ifє кращим вибором, означає, що використання коду #ifdefбуде компілювати -і запускати- навіть коли DEBUG_ENABLEDє помилковим. І набагато простіше відстежувати та видаляти вихідний налагодження, який виробляється, коли він не повинен бути, а навпаки.

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

#define DEBUG_ENABLED true

замість

#define DEBUG_ENABLED (1)

Константа не може використовуватися для вмикання / відключення налагодження, тому запускати #ifdef з #define до 0 не може бути настільки доброякісним. Що стосується істинних / хибних, то вони були додані в C99 і не існують у C89 / C90.
Майкл Карман

Мікель: Він / вона виступав проти використання #ifdef ?!
Джон Кейдж,

7
Так, одна з проблем #ifdefполягає в тому, що вона працює з речами, які не визначені; незалежно від того, чи вони визначені навмисно чи через помилку друку чи що у вас є.
bames53

6
Ваше додавання до відповіді неправильне. #if DEBUG_ENBALEDне є помилкою, виявленою препроцесором. Якщо DEBUG_ENBALEDце не визначено, він розширюється до маркера 0в #ifдирективах.
R .. GitHub СТОП ДОПОМОГА ВІД

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

56

Вони обоє огидні. Натомість зробіть це:

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

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


6
@MatthieuM. Власне, я думаю, що оригінальна версія була чудовою. Точка з комою трактуватиметься як порожній вислів. Однак забуття крапки з комою може зробити це небезпечним.
Кейсі Кубалл

31

#ifdef просто перевіряє, чи визначено маркер, заданий

#define FOO 0

тоді

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

20

У нас була така ж проблема в декількох файлах, і завжди є проблема, коли люди забувають включити файл "прапор функції" (з кодовою базою> 41 000 файлів це легко зробити).

Якщо у вас був функція.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 підтримує прапор, який викликає помилку для невизначених макросів ... але більшість сторонніх код або визначає, або не визначає функції, так що це не було б таким портативним.

Ми застосували портативний спосіб виправлення цього випадку, а також тестування на стан функції: макроси функції.

якщо ви змінили вказану вище функцію.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

15

Я думаю, що це повністю питання стилю. Жоден із насправді не має очевидної переваги перед іншим.

Послідовність важливіша, ніж будь-який конкретний вибір, тому я рекомендую вам зібратися зі своєю командою та вибрати один стиль та дотримуватися його.


8

Я сам віддаю перевагу:

#if defined(DEBUG_ENABLED)

Оскільки це спрощує створення коду, який шукає протилежну умову, набагато простіше помітити:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)

8
Особисто я думаю, що простіше пропустити цей маленький знак оклику!
Джон Кейдж,

6
З виділенням синтаксису? :) У підсвічуванні синтаксису "n" у "ifndef" помітити набагато складніше, оскільки він однакового кольору.
Джим Бак

Гаразд, я мав на увазі, що #ifndef простіше помітити, ніж #if! Визначено, коли ви порівнюєте з #if визначено .., але з урахуванням усіх #if визначено / # якщо! Визначено проти # ifdef / # ifndef, або однаково не читати!
Джон Кейдж

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

@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
tloach

1
У цьому суть, я можу або видалити DEBUG повністю або просто встановити його на 0, щоб відключити його.
Мартін Бекетт

так і має бути #define DEBUG 1. Ні#define DEBUG=1
Кешава GN

4

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

Цікаво. :-)


Я згоден, що макроси можуть бути небезпечними, але цей перший приклад був би досить очевидним для налагодження, і, звичайно, він дає лише одну помилку. Чому б ви очікували більше? Я бачив набагато грізніші помилки в результаті макросів ...
Джон Кейдж,

Правда, різниця між одним рішенням та іншим майже банальна. Але в цьому випадку, якщо ми говоримо про два конкуруючих стилі кодування, то навіть тривіальне не можна ігнорувати, оскільки після цього все, що залишилося, - це особистий смак (і в цей момент я вважаю, що це не повинно нормалізуватися )
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 

або ви можете встановити макрос як прапор компілятора. Також дивіться тут .


2

Перший здається мені більш зрозумілим. Здається більш природним зробити його прапором порівняно з визначеним / не визначеним.


2

Обидва цілком рівноцінні. У ідіоматичному використанні #ifdef використовується просто для перевірки визначеності (і що я б використовував у вашому прикладі), тоді як #if використовується у більш складних виразах, таких як #if визначено (A) &&! Визначено (B).


1
ОП не запитувало, що краще між "#ifdef" та "#if визначено", а між "# ifdef / # якщо визначено" та "#if".
хвостовик

1

Трохи ОТ, але ввімкнення / вимкнення журналу за допомогою препроцесора, безумовно, є оптимальним для C ++. Є приємні засоби реєстрації даних, такі як log4cxx Apache, які є відкритим кодом і не обмежують розповсюдження програми. Вони також дозволяють змінювати рівні журналу без перекомпіляції, мають дуже низькі накладні витрати, якщо ви вимикаєте реєстрацію, і дають вам можливість повністю вимкнути реєстрацію у виробництві.


1
Я погоджуюся, і ми насправді робимо це в нашому коді, я просто хотів приклад того, що ви можете використовувати #if і т.д. для
Джон Кейдж,

1

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

Хоча можна визначити макроси лише для Doxygen, це означає, що макроси в неактивних частинах коду також будуть задокументовані. Я особисто хочу показати перемикачі функцій і інакше документувати лише те, що вибрано на даний момент. Крім того, він робить код досить безладним, якщо є багато макросів, які потрібно визначити лише тоді, коли Doxygen обробляє файл.

Тому в цьому випадку краще завжди визначати макроси та використовувати #if.


0

Я завжди використовував прапори #ifdef і компілятор, щоб визначити це ...


Якась конкретна причина (з цікавості)?
Джон Кейдж,

2
Якщо чесно, я ніколи про це не замислювався - просто, як це робилося там, де я працював. Це дає перевагу в тому, що замість зміни коду для складання виробництва все, що вам потрібно зробити, це "зробити DEBUG" для налагодження, або "зробити ВИРОБНИЦТВО" для регулярного
tloach

0

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

Ось що говорить С ++ Gotchas Стівена С. Девхерста про використання # if's.


1
Це паршиве рішення, у нього є такі проблеми: 1. Працює лише у функціях, ви не можете видалити зайві змінні класу тощо. 2. Компілятори можуть кидати попередження про недоступний код 3. Код у випадку, якщо все-таки потрібно скомпілювати, що означає вам потрібно буде визначити всі свої функції налагодження тощо.
Дон Нойфельд,

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.