Warum nur ein Makro definieren, wenn es noch nicht definiert ist?

93

In unserer gesamten C-Code-Basis wird jedes Makro folgendermaßen definiert:

#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

Was ist der Grund für diese Definitionsprüfungen, anstatt nur die Makros zu definieren?

#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

Ich kann diese Praxis nirgendwo im Internet erklären.

Trevor Hickey
quelle
6
Das Ändern von Konstanten an einer anderen Stelle im Code funktioniert garantiert auf diese Weise. Wenn jemand anders eines dieser Makros definiert, wird er vom Präprozessor beim Parsen dieser Datei nicht überschrieben.
Enzo Ferber
8
Es ist ein Beispiel für das WET-Designprinzip.
stark
Wenn Sie eine Antwort mit einem Beispiel gepostet haben, versuchen Sie, sie zu kompilieren.
Enzo Ferber

Antworten:

141

Auf diese Weise können Sie die Makros beim Kompilieren überschreiben:

gcc -DMACRONAME=value

Die Definitionen in der Header-Datei werden als Standard verwendet.

Barmar
quelle
51

Stellen Sie sich diese Situation vor, wie ich im Kommentar sagte:

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

Wird gedruckt 44.

Wenn die Bedingung ifndefjedoch nicht vorhanden wäre, würde das Ergebnis Kompilierungswarnungen zur Neudefinition von MACRO sein und es wird gedruckt 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
quelle
1
Dies ist compilerspezifisch. Die Neudefinition eines objektähnlichen Makros ist illegal, es sei denn, die Neudefinition ist "dieselbe" (es gibt eine technischere Spezifikation dafür, aber dies ist hier nicht wichtig). Unzulässiger Code erfordert eine Diagnose, und nachdem der Compiler eine Diagnose (hier eine Warnung) ausgegeben hat, kann er alles tun, einschließlich des Kompilierens des Codes mit implementierungsspezifischen Ergebnissen.
Pete Becker
7
Wenn Sie widersprüchliche Defs für dasselbe Makro haben, würden Sie in den meisten Fällen nicht lieber die Warnung erhalten? Anstatt stillschweigend die erste Definition zu verwenden (weil die zweite eine verwendet ifdef, um eine Neudefinition zu vermeiden).
Peter Cordes
@PeterCordes In den meisten Fällen werden Definitionen unter #infdefs als "Fallback" - oder "Standard" -Werte verwendet. Grundsätzlich gilt: "Wenn der Benutzer es konfiguriert hat, ist dies in Ordnung. Wenn nicht, verwenden wir einen Standardwert."
Angew ist nicht mehr stolz auf SO
@Angew: Ok, wenn Sie also einige #definesin einem Bibliotheksheader haben, die Teil des ABI der Bibliothek sind, sollten Sie sie nicht einschließen #ifndef. (Oder besser, verwenden Sie eine enum). Ich wollte #ifndefnur klarstellen , dass dies nur dann angemessen ist, wenn eine benutzerdefinierte Definition für etwas in einer Kompilierungseinheit vorhanden ist, eine andere jedoch nicht. Wenn a.cHeader in einer anderen Reihenfolge als enthalten sind b.c, erhalten sie möglicherweise andere Definitionen von max(a,b)und eine dieser Definitionen kann mit brechen max(i++, x), während die andere möglicherweise temporäre Elemente in einem GNU-Anweisungsausdruck verwendet. Zumindest immer noch verwirrend!
Peter Cordes
@ PeterCordes Was ich in diesem Fall gerne mache, ist#ifdef FOO #error FOO already defined! #endif #define FOO x
Cole Johnson
17

Ich kenne den Kontext nicht, aber dies kann verwendet werden, um dem Benutzer die Verfügbarkeit zu geben, die durch diese Makrodefinitionen festgelegten Werte zu überschreiben. Wenn der Benutzer explizit einen anderen Wert für eines dieser Makros definiert, wird dieser anstelle der hier verwendeten Werte verwendet.

In g ++ können Sie beispielsweise das -DFlag während der Kompilierung verwenden, um einen Wert an ein Makro zu übergeben.

Ivaylo Strandjev
quelle
14

Dies geschieht, damit der Benutzer der Header-Datei die Definitionen aus seinem Code oder aus dem -D-Flag des Compilers überschreiben kann.


quelle
7

Jedes C-Projekt befindet sich in mehreren Quelldateien. Wenn Sie an einer einzelnen Quelldatei arbeiten, scheinen die Überprüfungen (und tatsächlich) keinen Sinn zu haben. Wenn Sie jedoch an einem großen C-Projekt arbeiten, empfiehlt es sich, vor dem Definieren einer Konstante nach vorhandenen Definitionen zu suchen. Die Idee ist einfach: Sie benötigen die Konstante in dieser bestimmten Quelldatei, sie wurde jedoch möglicherweise bereits in einer anderen definiert.

George
quelle
2

Sie können sich ein Framework / eine Bibliothek vorstellen, die dem Benutzer eine Standardvoreinstellung gibt, mit der der Benutzer sie kompilieren und bearbeiten kann. Diese Definitionen sind in verschiedenen Dateien verteilt, und dem Endbenutzer wird empfohlen, die Datei config.h einzuschließen, in der er die Werte konfigurieren kann. Wenn der Benutzer einige Definitionen vergessen hat, kann das System aufgrund der Voreinstellung weiterarbeiten.

LPs
quelle
1

Verwenden von

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

Ermöglicht dem Benutzer, den Wert des Makros mithilfe des Befehlszeilenarguments (in gcc / clang / VS) zu definieren -DBEEPTRIM_PITCH_RATE_DEGPS=0.3f.

Es gibt noch einen weiteren wichtigen Grund. Es ist ein Fehler, ein Präprozessor-Makro anders zu definieren. Siehe diese Antwort auf eine andere SO-Frage . Ohne die #ifndefÜberprüfung sollte der Compiler einen Fehler erzeugen, wenn -DBEEPTRIM_PITCH_RATE_DEGPS=0.3fer als Befehlszeilenargument im Compileraufruf verwendet wird.

R Sahu
quelle