Зачем определять макрос, только если он еще не определен?

93

Я вижу, что во всей нашей базе кода C каждый макрос определяется следующим образом:

#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-дизайна.
Stark
Выложил ответ с примером, попробуйте скомпилировать.
Энцо Фербер

Ответы:

141

Это позволяет вам переопределить макросы при компиляции:

gcc -DMACRONAME=value

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

Barmar
источник
51

Как я сказал в комментарии, представьте себе такую ​​ситуацию:

foo.h

#define FOO  4

defs.h

#ifndef FOO
#define FOO 6
#endif

#ifndef BAR
#define BAR 4
#endif

bar.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
Если у вас есть конфликтующие определения для одного и того же макроса, разве вы не предпочтете получать предупреждение в большинстве случаев? Вместо того, чтобы молча использовать первое определение (потому что второе использует, 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

Любой проект C находится в нескольких исходных файлах. При работе с одним исходным файлом проверки кажутся (и фактически) не имеют смысла, но при работе над большим проектом C рекомендуется проверять существующие определения перед определением константы. Идея проста: вам нужна константа в этом конкретном исходном файле, но она, возможно, уже была определена в другом.

Джордж
источник
2

Вы можете подумать о фреймворке / библиотеке, которая дает пользователю предустановку по умолчанию, которая позволяет пользователю компилировать и работать с ней. Эти определения распространяются в разных файлах, и конечному пользователю рекомендуется включить его файл config.h, где он может настроить его значения. Если пользователь забыл некоторые определения, система может продолжать работать из-за предустановки.

LP
источник
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он используется в качестве аргумента командной строки при вызове компилятора.

Р Саху
источник