Навіщо визначати макрос лише якщо він ще не визначений?


93

У всій нашій базі коду С я бачу, що кожен макрос визначається наступним чином:

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

#ifndef BEEPTRIM_ROLL_RATE_DEGPS
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#endif

#ifndef FORCETRIMRELEASE_HOLD_TIME_MS
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#endif

#ifndef TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f
#endif

Яке обгрунтування робити ці перевірки визначення, а не просто визначати макроси?

#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f

Я не можу знайти цю практику, пояснену в Інтернеті.


6
Зміна констант де-небудь ще в коді гарантовано спрацює таким чином. Якщо десь ще хтось визначає один із цих макросів, він не буде перезаписаний препроцесором при аналізі цього файлу.
Енцо Фербер

8
Це приклад принципу дизайну WET.
суворо

Опублікував відповідь із прикладом, спробуйте скласти його.
Енцо Фербер,

Відповіді:


141

Це дозволяє замінити макроси під час компіляції:

gcc -DMACRONAME=value

Визначення у файлі заголовка використовуються за замовчуванням.


51

Як я вже говорив у коментарі, уявіть собі таку ситуацію:

foo.h

#define FOO  4

defs.h

#ifndef FOO
#define FOO 6
#endif

#ifndef BAR
#define BAR 4
#endif

бар. c

#include "foo.h"
#include "defs.h"

#include <stdio.h>

int main(void)
{
    printf("%d%d", FOO, BAR);
    return 0;
}

Буде друкувати 44.

Однак, якщо умовного ifndefне було, результатом будуть попередження про компіляцію перевизначення MACRO, і воно надрукується 64.

$ gcc -o bar bar.c
In file included from bar.c:2:0:
defs.h:1:0: warning: "FOO" redefined [enabled by default]
 #define FOO 6
 ^
In file included from bar.c:1:0:
foo.h:1:0: note: this is the location of the previous definition
 #define FOO 4
 ^

1
Це специфічно для компілятора. Перевизначення об’єктоподібного макросу є незаконним, якщо перевизначення не є “однаковим” (для цього існує більш технічна специфікація, але тут це не важливо). Нелегальний код вимагає діагностики, і, випустивши діагностику (тут попередження), компілятор може робити що завгодно, включаючи компіляцію коду з конкретними результатами реалізації.
Піт Бекер

7
Якщо конфліктуючі DEFS за той же макрос, а чи не ви , а отримати попередження в більшості випадків? Замість того, щоб мовчки використовувати перше визначення (тому що друге використовує, ifdefщоб уникнути повторного визначення).
Пітер Кордес,

@PeterCordes У більшості випадків визначення під #infdefs використовуються як "резервні" або "за замовчуванням" значення. В основному, "якщо користувач налаштував це, добре. Якщо ні, давайте використовувати значення за замовчуванням".
Ендже вже не пишається SO

@Angew: Добре, так що якщо у вас є деякі #definesв заголовку бібліотеки , які є частиною ABI бібліотеки, ви повинні НЕ обернути їх в #ifndef. (Або краще, використовуйте enum). Я просто хотів уточнити, що #ifndefпідходить лише тоді, коли в одному блоці компіляції є власне визначення для чогось, але не інше - це нормально. Якщо a.cвключає заголовки в іншому порядку, ніж b.cвони, вони можуть отримати різні визначення max(a,b), і одне з цих визначень може розірватися max(i++, x), але інше може використовувати темпорарії у виразі GNU-виразу. Все ще принаймні бентежить!
Пітер Кордес,

@PeterCordes Що я люблю робити в такому випадку#ifdef FOO #error FOO already defined! #endif #define FOO x
Коул Джонсон,

17

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

Наприклад, у g ++ ви можете використовувати -Dпрапор під час компіляції, щоб передати значення макросу.


14

Це робиться для того, щоб користувач файлу заголовка міг змінити визначення з його / її коду або з прапора компілятора -D.


7

Будь-який проект С розміщений на декількох вихідних файлах. Під час роботи над одним вихідним файлом перевірки, здається, (і насправді) не мають сенсу, але при роботі над великим проектом С добрею практикою є перевірка наявних визначень, перш ніж визначати константу. Ідея проста: вам потрібна константа у цьому конкретному вихідному файлі, але, можливо, вона вже була визначена в іншому.


2

Ви можете подумати про фреймворк / бібліотеку, яка надає користувачеві стандартну настройку, яка дозволяє користувачеві компілювати та працювати над ним. Ці визначення поширюються у різних файлах, і кінцевому користувачеві рекомендується включити файл config.h, де він може налаштувати його значення. Якщо користувач забув дещо визначити, система може продовжувати працювати через попередньо встановлену.


1

Використання

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

дозволяє користувачеві визначити значення макросу за допомогою аргументу командного рядка (у gcc / clang / VS) -DBEEPTRIM_PITCH_RATE_DEGPS=0.3f .

Є ще одна важлива причина. Помилковим є повторне визначення макросу препроцесора інакше. Дивіться цю відповідь на інше питання SO . Без #ifndefперевірки компілятор повинен створювати помилку, якщо -DBEEPTRIM_PITCH_RATE_DEGPS=0.3fвін використовується як аргумент командного рядка у виклику компілятора.

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