Po co definiować tylko makro, jeśli nie zostało jeszcze zdefiniowane?

93

W całej naszej bazie kodu C widzę każde makro zdefiniowane w następujący sposób:

#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

Jakie jest uzasadnienie wykonywania tych kontroli definicji zamiast tylko definiowania makr?

#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

Nie mogę znaleźć wyjaśnienia tej praktyki w sieci.

Trevor Hickey
źródło
6
Zmiana stałych w innym miejscu kodu na pewno zadziała w ten sposób. Jeśli w innym miejscu ktoś zdefiniuje jedno z tych makr, nie zostaną one nadpisane przez preprocesor podczas analizowania tego pliku.
Enzo Ferber
8
To przykład zasady projektowania WET.
ostry
Zamieszczono odpowiedź z przykładem, spróbuj ją skompilować.
Enzo Ferber

Odpowiedzi:

141

Pozwala to na przesłonięcie makr podczas kompilacji:

gcc -DMACRONAME=value

Definicje w pliku nagłówkowym są używane jako domyślne.

Barmar
źródło
51

Jak powiedziałem w komentarzu, wyobraź sobie taką sytuację:

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

Wydrukuje 44.

Jeśli jednak warunek ifndefnie istnieje, wynikiem byłyby ostrzeżenia kompilacji o redefinicji MACRO i zostanie wydrukowane 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
 ^
Enzo Ferber
źródło
1
To jest specyficzne dla kompilatora. Ponowne definiowanie makra obiektowego jest niedozwolone, chyba że redefinicja jest „taka sama” (jest do tego bardziej techniczna specyfikacja, ale nie jest to ważne). Nielegalny kod wymaga diagnostyki i po wydaniu diagnostyki (tutaj ostrzeżenie) kompilator może zrobić wszystko, w tym skompilować kod z wynikami specyficznymi dla implementacji.
Pete Becker
7
Jeśli masz sprzeczne definicje dla tego samego makra, czy nie wolałbyś raczej otrzymywać ostrzeżenia w większości przypadków? Zamiast po cichu używać pierwszej definicji (ponieważ druga używa an, ifdefaby uniknąć ponownego zdefiniowania).
Peter Cordes,
@PeterCordes W większości przypadków definicje pod #infdefs są używane jako wartości „rezerwowe” lub „domyślne”. Zasadniczo, „jeśli użytkownik to skonfigurował, w porządku. Jeśli nie, użyjmy wartości domyślnej”.
Angew nie jest już dumny z SO
@Angew: Ok, więc jeśli masz jakiś #definesw bibliotece nagłówka, które są częścią ABI biblioteki, należy nie zawijać je #ifndef. (Lub lepiej, użyj enum). Chciałem tylko wyjaśnić, że #ifndefjest to właściwe tylko wtedy, gdy posiadanie niestandardowej definicji czegoś w jednej jednostce kompilacji, a nie inna, jest w porządku. Jeśli a.czawiera nagłówki w innej kolejności niż b.c, mogą otrzymać inne definicje max(a,b), a jedna z tych definicji może się zrywać max(i++, x), ale druga może używać tymczasowych w wyrażeniu-instrukcji GNU. Wciąż co najmniej zagmatwane!
Peter Cordes,
@PeterCordes W takim przypadku lubię robić#ifdef FOO #error FOO already defined! #endif #define FOO x
Cole Johnson,
17

Nie znam kontekstu, ale można go użyć, aby dać użytkownikowi możliwość zastąpienia wartości ustawionych przez te definicje makr. Jeśli użytkownik wyraźnie zdefiniuje inną wartość dla któregokolwiek z tych makr, zostanie ona użyta zamiast użytych tutaj wartości.

Na przykład w g ++ możesz użyć -Dflagi podczas kompilacji, aby przekazać wartość do makra.

Ivaylo Strandjev
źródło
14

Odbywa się to po to, aby użytkownik pliku nagłówkowego mógł przesłonić definicje ze swojego kodu lub z flagi -D kompilatora.


źródło
7

Każdy projekt w języku C znajduje się w wielu plikach źródłowych. Podczas pracy z pojedynczym plikiem źródłowym sprawdzenia wydają się (i faktycznie) nie mają sensu, ale podczas pracy nad dużym projektem C dobrą praktyką jest sprawdzenie istniejących definicji przed zdefiniowaniem stałej. Pomysł jest prosty: potrzebujesz stałej w tym konkretnym pliku źródłowym, ale mogła być już zdefiniowana w innym.

Jerzy
źródło
2

Możesz pomyśleć o frameworku / bibliotece, która daje użytkownikowi domyślne ustawienie wstępne, które pozwala użytkownikowi na kompilację i pracę nad nim. Te definicje są rozmieszczone w różnych plikach, a końcowemu użytkownikowi zaleca się dołączenie swojego pliku config.h, w którym może skonfigurować jego wartości. Jeśli użytkownik zapomniał o jakimś zdefiniowaniu, system może kontynuować pracę z powodu ustawienia wstępnego.

Płyty LP
źródło
1

Za pomocą

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

umożliwia użytkownikowi zdefiniowanie wartości makra za pomocą argumentu wiersza poleceń (w gcc / clang / VS) -DBEEPTRIM_PITCH_RATE_DEGPS=0.3f.

Jest jeszcze jeden ważny powód. Błędem jest ponowne definiowanie makra preprocesora w inny sposób. Zobacz tę odpowiedź na inne pytanie SO . Bez #ifndefsprawdzenia kompilator powinien wygenerować błąd, jeśli -DBEEPTRIM_PITCH_RATE_DEGPS=0.3fzostanie użyty jako argument wiersza polecenia w wywołaniu kompilatora.

R Sahu
źródło