Welches ist unter den folgenden Aussagen in C besser zu verwenden?
static const int var = 5;
oder
#define var 5
oder
enum { var = 5 };
Welches ist unter den folgenden Aussagen in C besser zu verwenden?
static const int var = 5;
oder
#define var 5
oder
enum { var = 5 };
Antworten:
Es hängt davon ab, wofür Sie den Wert benötigen. Sie (und alle anderen bisher) haben die dritte Alternative ausgelassen:
static const int var = 5;
#define var 5
enum { var = 5 };
Ignorieren Sie Probleme bei der Wahl des Namens.
Ziehen Sie daher in den meisten Kontexten die Aufzählung den Alternativen vor. Andernfalls sind wahrscheinlich der erste und der letzte Aufzählungspunkt die bestimmenden Faktoren - und Sie müssen genauer überlegen, ob Sie beide gleichzeitig erfüllen müssen.
Wenn Sie nach C ++ fragen, verwenden Sie jedes Mal Option (1) - die statische Konstante.
enum
ist, dass sie als int
([C99] 6.7.2.2/3) implementiert sind. Mit A #define
können Sie vorzeichenlose und lange mit U
und L
Suffixe angeben und const
einen Typ angeben . enum
kann Probleme mit üblichen Typkonvertierungen verursachen.
enum
noch #define
zusätzlichen Platz an sich verbraucht. Der Wert wird im Objektcode als Teil der Anweisungen angezeigt und nicht als Speicher im Datensegment, im Heap oder auf dem Stapel zugewiesen. Sie haben etwas Speicherplatz für den static const int
, aber der Compiler kann ihn möglicherweise optimieren, wenn Sie keine Adresse angeben.
enum
s (und static const
): Sie können nicht geändert werden. a define
kann #undefine
'd sein, wobei an enum
und static const
auf den angegebenen Wert festgelegt sind.
Allgemein gesagt:
static const
Weil es den Umfang respektiert und typsicher ist.
Die einzige Einschränkung, die ich sehen konnte: Wenn Sie möchten, dass die Variable möglicherweise in der Befehlszeile definiert wird. Es gibt noch eine Alternative:
#ifdef VAR // Very bad name, not long enough, too general, etc..
static int const var = VAR;
#else
static int const var = 5; // default value
#endif
Verwenden Sie nach Möglichkeit anstelle von Makros / Auslassungspunkten eine typsichere Alternative.
Wenn Sie wirklich mit einem Makro arbeiten müssen (zum Beispiel möchten __FILE__
oder __LINE__
), sollten Sie Ihr Makro SEHR sorgfältig benennen : In seiner Namenskonvention empfiehlt Boost alle Großbuchstaben, beginnend mit dem Namen des Projekts (hier BOOST_ ), während Sie die Bibliothek durchsuchen, werden Sie feststellen, dass (im Allgemeinen) der Name des bestimmten Bereichs (der Bibliothek) gefolgt von einem aussagekräftigen Namen folgt.
Es macht in der Regel für lange Namen :)
static
übrig bleiben, deren Adresse übernommen wird. und wenn die Adresse genommen wird, hätte man kein #define
oder enum
(keine Adresse) verwenden können ... also sehe ich wirklich nicht, welche Alternative hätte verwendet werden können. Wenn Sie die "Auswertung der Kompilierungszeit" abschaffen können, suchen Sie möglicherweise extern const
stattdessen nach.
#if
könnte über vorzuziehen sein , #ifdef
für boolean Flaggen, aber in diesem Fall wäre es unmöglich machen zu definieren , var
wie 0
von der Kommandozeile. Also in diesem Fall #ifdef
sinnvoller, solange 0
ein rechtlicher Wert für var
.
Speziell in C? In C lautet die richtige Antwort: verwenden #define
(oder gegebenenfalls)enum
)
Während es const
in der Realität vorteilhaft ist, die Scoping- und Typisierungseigenschaften eines Objekts zu habenconst
Objekte in C (im Gegensatz zu C ++) keine echten Konstanten und daher in den meisten praktischen Fällen normalerweise unbrauchbar.
In C sollte die Auswahl also davon abhängen, wie Sie Ihre Konstante verwenden möchten. Beispielsweise können Sie ein const int
Objekt nicht als case
Beschriftung verwenden (solange ein Makro funktioniert). Sie können ein const int
Objekt nicht als Bitfeldbreite verwenden (während ein Makro funktioniert). In C89 / 90 können Sie kein const
Objekt verwenden, um eine Arraygröße anzugeben (während ein Makro funktioniert). Selbst in C99 können Sie kein const
Objekt verwenden, um eine Arraygröße anzugeben, wenn Sie ein Nicht- VLA- Array benötigen .
Wenn dies für Sie wichtig ist, bestimmt es Ihre Wahl. Meistens haben Sie keine andere Wahl, als #define
in C zu verwenden . Und vergessen Sie nicht eine andere Alternative, die echte Konstanten in C - erzeugt enum
.
In C ++ sind const
Objekte echte Konstanten, daher ist es in C ++ fast immer besser, die const
Variante zu bevorzugen ( in C ++ ist dies jedoch nicht explizit erforderlich static
).
const int
Objekten in Fallbezeichnungen ist in allen Versionen der C-Sprache unzulässig. (Natürlich kann Ihr Compiler ihn als nicht standardmäßige C ++ - ähnliche Spracherweiterung unterstützen.)
const
bedeutet schreibgeschützt. const int r = rand();
ist vollkommen legal.
constexpr
als const
speziell mit den stl
Containern wie array
oder bitset
.
switch()
Aussage getestet haben , nicht in case
einer. Ich bin auch gerade dabei gewesen caught
Der Unterschied zwischen static const
und #define
besteht darin, dass der erstere den Speicher verwendet und der letztere den Speicher nicht zum Speichern verwendet. Zweitens können Sie die Adresse von a nicht übergeben, #define
während Sie die Adresse von a übergeben könnenstatic const
. Je nachdem, unter welchen Umständen wir uns befinden, müssen wir einen dieser beiden auswählen. Beide sind unter verschiedenen Umständen von ihrer besten Seite. Bitte nimm nicht an, dass einer besser ist als der andere ... :-)
Wenn das der Fall gewesen wäre, hätte Dennis Ritchie den Besten alleine gelassen ... hahaha ... :-)
const
Speicher verwendet. GCC (getestet mit 4.5.3 und einigen neueren Versionen) optimiert const int
bei Verwendung von -O3 problemlos das direkte Literal in Ihrem Code. Wenn Sie also eine eingebettete Entwicklung mit wenig RAM (z. B. AVR) durchführen, können Sie C-Konstanten sicher verwenden, wenn Sie GCC oder einen anderen kompatiblen Compiler verwenden. Ich habe es nicht getestet, erwarte aber, dass Clang übrigens dasselbe tut.
In C #define
ist viel beliebter. Sie können diese Werte zum Deklarieren von Arraygrößen verwenden, zum Beispiel:
#define MAXLEN 5
void foo(void) {
int bar[MAXLEN];
}
ANSI C erlaubt Ihnen meines Wissens nicht, static const
s in diesem Zusammenhang zu verwenden . In C ++ sollten Sie in diesen Fällen Makros vermeiden. Du kannst schreiben
const int maxlen = 5;
void foo() {
int bar[maxlen];
}
und sogar weglassen, static
weil die interne Verknüpfung const
bereits von [nur in C ++] impliziert wird .
const int MY_CONSTANT = 5;
in einer Datei haben und in einer extern const int MY_CONSTANT;
anderen darauf zugreifen . Ich konnte im Standard (mindestens C99) keine Informationen zum const
Ändern des Standardverhaltens finden. "6.2.2: 5 Wenn die Deklaration eines Bezeichners für ein Objekt einen Dateibereich und keinen Speicherklassenspezifizierer hat, ist seine Verknüpfung extern."
bar
ist ein VLA (Array variabler Länge); Der Compiler generiert wahrscheinlich Code, als ob seine Länge konstant wäre.
Ein weiterer Nachteil von const
C ist, dass Sie den Wert nicht zum Initialisieren eines anderen verwenden können const
.
static int const NUMBER_OF_FINGERS_PER_HAND = 5;
static int const NUMBER_OF_HANDS = 2;
// initializer element is not constant, this does not work.
static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND
* NUMBER_OF_HANDS;
Auch dies funktioniert nicht mit einer Konstante, da der Compiler sie nicht als Konstante betrachtet:
static uint8_t const ARRAY_SIZE = 16;
static int8_t const lookup_table[ARRAY_SIZE] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant!
Ich würde gerne const
in diesen Fällen getippt verwenden, sonst ...
static uint8_t const ARRAY_SIZE = 16;
kann eine Herausforderung sein, herauszufinden, warum Sie plötzlich nicht mehr kompiliert werden, insbesondere wenn die #define ARRAY_SIZE 256
zehn Ebenen tief in einem Wirrwarr von Headern vergraben sind. Der Name aller Großbuchstaben ARRAY_SIZE
bittet um Ärger. Reservieren Sie ALL_CAPS für Makros und definieren Sie niemals ein Makro, das nicht in ALL_CAPS-Form vorliegt.
const
. Dies könnte mehr positiv bewertet werden!
Wenn Sie damit durchkommen können, static const
hat das viele Vorteile. Es folgt den normalen Gültigkeitsbereichsprinzipien, ist in einem Debugger sichtbar und befolgt im Allgemeinen die Regeln, denen Variablen folgen.
Zumindest im ursprünglichen C-Standard ist dies jedoch keine Konstante. Wenn Sie verwenden #define var 5
, können Sie int foo[var];
als Deklaration schreiben , aber das können Sie nicht (außer als Compiler-Erweiterung "mit static const int var = 5;
. Dies ist in C ++ nicht der Fall, wo die static const
Version überall verwendet werden #define
kann, wo die Version kann, und ich glaube das ist auch bei C99 der Fall.
Benennen Sie jedoch niemals eine #define
Konstante mit einem Kleinbuchstaben. Jede mögliche Verwendung dieses Namens wird bis zum Ende der Übersetzungseinheit außer Kraft gesetzt. Makrokonstanten sollten sich in einem eigenen Namespace befinden, der traditionell nur aus Großbuchstaben besteht, möglicherweise mit einem Präfix.
const
in C99 ist immer noch keine echte Konstante. Sie können die Arraygröße mit a const
in C99 deklarieren , jedoch nur, weil C99 Arrays mit variabler Länge unterstützt. Aus diesem Grund funktioniert es nur, wenn VLAs zulässig sind. Selbst in C99 können Sie beispielsweise a nicht verwenden, const
um die Größe eines Member-Arrays in a zu deklarieren struct
.
const int
Größe perfekt initialisieren, als wäre es eine C ++ - Konstante oder ein Makro. Ob Sie sich auf diese Abweichung von GCC vom Standard verlassen möchten, ist natürlich Ihre Wahl. Ich persönlich würde mich dafür entscheiden, es sei denn, Sie können wirklich die Verwendung eines anderen Compilers als GCC oder Clang vorhersehen. Letzterer hat hier die gleiche Funktion (getestet mit Clang) 3.7).
Es ist IMMER vorzuziehen, const anstelle von #define zu verwenden. Dies liegt daran, dass const vom Compiler und #define vom Präprozessor behandelt wird. Es ist, als ob #define selbst nicht Teil des Codes ist (grob gesagt).
Beispiel:
#define PI 3.1416
Der symbolische Name PI wird von Compilern möglicherweise nie gesehen. Es kann vom Präprozessor entfernt werden, bevor der Quellcode überhaupt zu einem Compiler gelangt. Infolgedessen wird der Name PI möglicherweise nicht in die Symboltabelle eingegeben. Dies kann verwirrend sein, wenn beim Kompilieren ein Fehler auftritt, bei dem die Konstante verwendet wird, da sich die Fehlermeldung möglicherweise auf 3.1416 und nicht auf PI bezieht. Wenn PI in einer Header-Datei definiert wäre, die Sie nicht geschrieben haben, hätten Sie keine Ahnung, woher diese 3.1416 stammt.
Dieses Problem kann auch in einem symbolischen Debugger auftreten, da der Name, mit dem Sie programmieren, möglicherweise nicht in der Symboltabelle enthalten ist.
Lösung:
const double PI = 3.1416; //or static const...
#define var 5
wird Ihnen Probleme bereiten, wenn Sie Dinge wie haben mystruct.var
.
Zum Beispiel,
struct mystruct {
int var;
};
#define var 5
int main() {
struct mystruct foo;
foo.var = 1;
return 0;
}
Der Präprozessor ersetzt es und der Code wird nicht kompiliert. Aus diesem Grund schlägt der traditionelle Codierungsstil vor, dass alle Konstanten #define
Großbuchstaben verwenden, um Konflikte zu vermeiden.
Ich habe ein schnelles Testprogramm geschrieben, um einen Unterschied zu demonstrieren:
#include <stdio.h>
enum {ENUM_DEFINED=16};
enum {ENUM_DEFINED=32};
#define DEFINED_DEFINED 16
#define DEFINED_DEFINED 32
int main(int argc, char *argv[]) {
printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED);
return(0);
}
Dies kompiliert mit diesen Fehlern und Warnungen:
main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
enum {ENUM_DEFINED=32};
^
main.c:5:7: note: previous definition is here
enum {ENUM_DEFINED=16};
^
main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
#define DEFINED_DEFINED 32
^
main.c:8:9: note: previous definition is here
#define DEFINED_DEFINED 16
^
Beachten Sie, dass enum einen Fehler ausgibt, wenn define eine Warnung ausgibt.
Die Definition
const int const_value = 5;
definiert nicht immer einen konstanten Wert. Einige Compiler (z. B. tcc 0.9.26 ) weisen nur Speicher zu, der mit dem Namen "const_value" gekennzeichnet ist. Mit dem Bezeichner "const_value" können Sie diesen Speicher nicht ändern. Sie können den Speicher jedoch weiterhin mit einer anderen Kennung ändern:
const int const_value = 5;
int *mutable_value = (int*) &const_value;
*mutable_value = 3;
printf("%i", const_value); // The output may be 5 or 3, depending on the compiler.
Dies bedeutet die Definition
#define CONST_VALUE 5
Nur so kann ein konstanter Wert definiert werden, der auf keinen Fall geändert werden kann.
#define
können Sie dies auch ändern, indem Sie den Maschinencode bearbeiten.
5
. Aber man kann nicht ändern, #define
weil es ein Präprozessor-Makro ist. Es existiert nicht im Binärprogramm. Wenn man alle Stellen ändern wollte, an denen sie CONST_VALUE
verwendet wurden, musste man dies einzeln tun.
#define CONST 5
, if (CONST == 5) { do_this(); } else { do_that(); }
und der Compiler entfernt den else
Zweig. Wie schlagen Sie vor, den Maschinencode zu bearbeiten, um ihn CONST
auf 6 zu ändern ?
#define
ist nicht kugelsicher.
#define
. Der einzige wirkliche Weg, dies zu tun, besteht darin, den Quellcode zu bearbeiten und neu zu kompilieren.
Obwohl es sich um Ganzzahlen handelte, ist es erwähnenswert, dass #define und enums nutzlos sind, wenn Sie eine konstante Struktur oder Zeichenfolge benötigen. Diese werden normalerweise als Zeiger an Funktionen übergeben. (Mit Strings ist es erforderlich; mit Strukturen ist es viel effizienter.)
Bei Ganzzahlen müssen Sie sich in einer eingebetteten Umgebung mit sehr begrenztem Speicher möglicherweise Gedanken darüber machen, wo die Konstante gespeichert ist und wie die Zugriffe darauf kompiliert werden. Der Compiler fügt zur Laufzeit möglicherweise zwei Konstanten hinzu, zur Kompilierungszeit jedoch zwei #defines. Eine # define-Konstante kann in eine oder mehrere MOV-Anweisungen (sofort) konvertiert werden, was bedeutet, dass die Konstante effektiv im Programmspeicher gespeichert ist. Eine const-Konstante wird im Abschnitt .const im Datenspeicher gespeichert. In Systemen mit einer Harvard-Architektur kann es zu Unterschieden in der Leistung und der Speichernutzung kommen, obwohl diese wahrscheinlich gering sind. Sie könnten für die Hardcore-Optimierung innerer Schleifen von Bedeutung sein.
Denken Sie nicht, dass es eine Antwort für "was immer am besten ist" gibt, aber wie Matthieu sagte
static const
ist typsicher. Mein größter Ärger mit Haustieren #define
ist jedoch, dass Sie beim Debuggen in Visual Studio die Variable nicht sehen können. Es gibt einen Fehler, dass das Symbol nicht gefunden werden kann.
Übrigens #define
ist "enum" eine Alternative zu , die ein angemessenes Scoping bietet, sich aber wie eine "echte" Konstante verhält. Zum Beispiel:
enum {number_ten = 10;}
In vielen Fällen ist es hilfreich, Aufzählungstypen zu definieren und Variablen dieser Typen zu erstellen. In diesem Fall können Debugger möglicherweise Variablen entsprechend ihrem Aufzählungsnamen anzeigen.
Eine wichtige Einschränkung dabei: In C ++ sind Aufzählungstypen nur eingeschränkt mit Ganzzahlen kompatibel. Zum Beispiel kann man standardmäßig keine Arithmetik für sie durchführen. Ich finde, dass dies ein merkwürdiges Standardverhalten für Aufzählungen ist. Während es schön gewesen wäre, einen "strengen Aufzählungstyp" zu haben, würde ich denken, dass das Standardverhalten eines "Aufzählungstyps" mit ganzen Zahlen austauschbar sein sollte, da C ++ im Allgemeinen mit C kompatibel sein soll.
int
, daher kann der "Enum-Hack" nicht mit anderen Ganzzahltypen verwendet werden. (Die Aufzählung Typ ist kompatibel mit einiger Implementierung definiert Integer - Typ, nicht unbedingt int
, aber in diesem Fall ist der Typ ist anonym , so dass spielt keine Rolle.)
int
eine Variable vom Typ Aufzählung zuweist (was Compiler tun dürfen) und man versucht, einer solchen Variablen zuzuweisen ein Mitglied seiner eigenen Aufzählung. Ich wünschte, Standardkomitees würden tragbare Methoden zum Deklarieren von Ganzzahltypen mit spezifizierter Semantik hinzufügen. JEDE Plattform sollte unabhängig von ihrer char
Größe in der Lage sein, z. B. einen Typ zu deklarieren, der den Mod 65536 umschließt, selbst wenn der Compiler viele AND R0,#0xFFFF
oder gleichwertige Anweisungen hinzufügen muss .
uint16_t
, obwohl dies natürlich kein Aufzählungstyp ist. Es wäre schön, wenn der Benutzer den Ganzzahltyp angeben würde, der zur Darstellung eines bestimmten Aufzählungstyps verwendet wird, aber Sie können mit einem typedef
for uint16_t
und einer Reihe von #define
s für einzelne Werte weitgehend den gleichen Effekt erzielen .
2U < -1L
als wahr und andere als falsch bewertet werden , und wir bleiben jetzt bei der Tatsache, dass einige Plattformen einen Vergleich zwischen uint32_t
und int32_t
als signiert implementieren und einige als nicht signiert, aber das bedeutet nicht, dass das Komitee keinen aufwärtskompatiblen Nachfolger für C definieren konnte, der Typen enthält, deren Semantik auf allen Compilern konsistent wäre.
Ein einfacher Unterschied:
Zur Vorverarbeitungszeit wird die Konstante durch ihren Wert ersetzt. Sie können den Dereferenzierungsoperator also nicht auf eine Definition anwenden, aber Sie können den Dereferenzierungsoperator auf eine Variable anwenden.
Wie Sie annehmen würden, ist define schneller als statische const.
Zum Beispiel mit:
#define mymax 100
du kannst nicht tun printf("address of constant is %p",&mymax);
.
Aber mit
const int mymax_var=100
du kannst tun printf("address of constant is %p",&mymax_var);
.
Um es klarer zu machen, wird die Definition in der Vorverarbeitungsphase durch ihren Wert ersetzt, sodass keine Variable im Programm gespeichert ist. Wir haben nur den Code aus dem Textsegment des Programms, in dem die Definition verwendet wurde.
Für statische Konstanten haben wir jedoch eine Variable, die irgendwo zugeordnet ist. Für gcc werden statische Konstanten im Textsegment des Programms zugewiesen.
Oben wollte ich etwas über den Referenzoperator erzählen, also ersetze die Dereferenzierung durch die Referenz.
const
Qualifikationsmerkmal hat. C hat keine anderen Symbolikkonstanten als Enum-Konstanten . A const int
ist eine Variable. Sie verwechseln auch die Sprache und bestimmte Implementierungen. Es ist nicht erforderlich, wo das Objekt platziert werden soll. Und es gilt nicht einmal für gcc: Normalerweise werden const
qualifizierte Variablen in den .rodata
Abschnitt eingefügt. Das hängt aber von der Zielplattform ab. Und Sie meinen die Adresse des Betreibers &
.
Wir haben uns den erzeugten Assembler-Code auf dem MBF16X angesehen ... Beide Varianten führen zu demselben Code für arithmetische Operationen (z. B. ADD Immediate).
Wird const int
also für die Typprüfung bevorzugt, während #define
es sich um einen alten Stil handelt. Vielleicht ist es compilerspezifisch. Überprüfen Sie also Ihren erstellten Assembler-Code.
Ich bin mir nicht sicher, ob ich Recht habe, aber meiner Meinung nach rufe ich an #define
d value viel schneller als das Aufrufen einer anderen normalerweise deklarierten Variablen (oder eines konstanten Werts). Dies liegt daran, dass das Programm, wenn es ausgeführt wird und eine normalerweise deklarierte Variable verwenden muss, an die genaue Stelle im Speicher springen muss, um diese Variable abzurufen.
Im Gegensatz dazu #define
muss das Programm, wenn es den Wert d verwendet, nicht zu einem zugewiesenen Speicher springen, sondern nimmt nur den Wert. Wenn #define myValue 7
und das Programm aufrufen myValue
, verhält es sich genauso wie beim Aufrufen 7
.