„Static const“ vs. „#define“ für Effizienz in C

Lesezeit: 12 Minuten

Benutzer-Avatar
Benutzer3282276

Ich habe mich kürzlich gefragt, was der Unterschied zwischen ist #define und static const ist in C und warum es zwei Methoden gibt, um die gleichen Dinge zu tun. Ich habe hier einige Leute gefunden, die ähnliche Fragen hatten:

  • Vor- und Nachteile von #define vs. Konstanten?

  • “statische Konstante” vs. “#define” vs. “enum”

  • Statische Konstante vs. #define

Viele Leute sprechen über bewährte Verfahren und Konventionen und geben praktische Gründe für die Verwendung einer über der anderen an, z. B. die Notwendigkeit, einen Zeiger auf eine Konstante zu übergeben, was ich mit a tun kann static const aber nicht mit a #define. Ich habe jedoch noch niemanden gefunden, der über einen Vergleich der Effizienz der beiden spricht.

Soweit ich das verstehe C Präprozessor, wenn ich eine Aussage wie diese habe:

#define CONSTANT 6

Ich schaffe einen konstanten Wert, der so verwendet werden kann

char[CONSTANT] die tatsächlich durch diese Anweisung ersetzt wird char[6] vor der eigentlichen Zusammenstellung.

Dies scheint mir effizienter zu sein als die Verwendung von a
static const constant = 6; weil dies eine Variable namens Konstante erstellen würde, die auf dem Stapel leben würde, von der ich annehme, dass sie mit etwas mehr Gepäck als a kommen würde #define. Angenommen, ich brauche eine Konstante in einer Situation, in der ich wählen könnte, ob ich einen Präprozessor verwenden möchte #define oder ein static const Aussage ohne offensichtliche Gründe, eine der beiden vorzuziehen, was ist effizienter? Und wie genau würde ich vorgehen, um das selbst zu testen?

  • Ich sehe keinen Grund, warum ein Compiler a nicht ersetzen sollte static const int foo = 6 mit wörtlich 6 zur Kompilierzeit.

    – Philipp

    21. November 2014 um 18:59 Uhr


  • die #define’d-Konstante würde wirklich nur in der Quelle und im Compiler existieren. Sein Wert würde überall dort eingefügt werden, wo er verwendet wird, z foo(constant). a static const constant WÜRDE der Variablen selbst einen permanenten Speicher zuweisen, und jeder Ort, an dem sie verwendet wird, würde sich auf diesen einen Wert beziehen. Für ein einfaches Zeichen ist das eine triviale Einsparung – der Variablenzeiger / die Referenz ist intern größer als ein “Zeichen”. Aber wenn dieser Wert viel länger wäre, dann wäre die Konstante billiger.

    – Markus B

    21. November 2014 um 19:01 Uhr


  • Das unterstellen Sie constant würde auf dem Stapel leben. Ein optimierender Compiler kann (und wird im Allgemeinen) einfach den Namen ersetzen constant mit dem Wert 6, genau wie bei einem Makro. Also nein, Sie werden durch die Verwendung eines Makros keine Geschwindigkeit gewinnen (und selbst wenn es einen Unterschied machen würde, sprechen Sie von einer Mikrooptimierung, die höchstwahrscheinlich vernachlässigbare Auswirkungen auf die Leistung Ihres Codes hätte).

    – Maisstängel

    21. November 2014 um 19:03 Uhr

  • @ Philipp int const *p = &foo; wäre ein Grund, aber ich denke, wir werden es nie erfahren, da wir keine haben Code.

    – WhozCraig

    21. November 2014 um 19:03 Uhr

  • Wenn der Wert vom Typ ist intkönnen Sie den Enum-Hack verwenden: enum { CONSTANT = 6 };

    – Keith Thompson

    21. November 2014 um 19:23 Uhr

Benutzer-Avatar
Venkatesh

Betrachten Sie die folgenden 2 Testdateien

Test1.c: Verwendet die statische Konstante foo.

// Test1.c uses static const..

#include <stdio.h>

static const foo = 6;

int main() {
    printf("%d", foo);
    return 0;
}

Test2.c: verwendet Makro.

// Test2.c uses macro..

#include <stdio.h>

#define foo 6

int main() {
    printf("%d", foo);
    return 0;
}

und die entsprechenden Montageäquivalenzen bei der Verwendung gcc -O0(Standard) sind folgende,

Assemblierung für Test1.c:

  0000000000000000 <main>:
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 20             sub    rsp,0x20
   8:   e8 00 00 00 00          call   d <main+0xd>
   d:   b8 06 00 00 00          mov    eax,0x6
  12:   89 c2                   mov    edx,eax
  14:   48 8d 0d 04 00 00 00    lea    rcx,[rip+0x4]        # 1f <main+0x1f>
  1b:   e8 00 00 00 00          call   20 <main+0x20>
  20:   b8 00 00 00 00          mov    eax,0x0
  25:   48 83 c4 20             add    rsp,0x20
  29:   5d                      pop    rbp
  2a:   c3                      ret
  2b:   90                      nop

Assemblierung für Test2.c:

  0000000000000000 <main>:
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 20             sub    rsp,0x20
   8:   e8 00 00 00 00          call   d <main+0xd>
   d:   ba 06 00 00 00          mov    edx,0x6
  12:   48 8d 0d 00 00 00 00    lea    rcx,[rip+0x0]        # 19 <main+0x19>
  19:   e8 00 00 00 00          call   1e <main+0x1e>
  1e:   b8 00 00 00 00          mov    eax,0x0
  23:   48 83 c4 20             add    rsp,0x20
  27:   5d                      pop    rbp
  28:   c3                      ret
  29:   90                      nop

In beiden Fällen wird kein externer Speicher verwendet. Aber der Unterschied ist, dass #define ersetzt foo nach Wert, static const ist eine Anweisung, also erhöht sie den Anweisungszeiger auf die nächste Anweisung und verwendet 1 zusätzliches Register, um den Wert zu speichern.

Damit können wir sagen, dass Makros besser sind als statische Konstanten, aber der Unterschied ist minimal.

BEARBEITEN: Beim Benutzen -O3 Kompilierungsoption (dh bei eingeschalteter Optimierung) wertet sowohl test1.c als auch test2.c gleich aus.

0000000000000000 <main>:
   0:   48 83 ec 28             sub    rsp,0x28
   4:   e8 00 00 00 00          call   9 <main+0x9>
   9:   48 8d 0d 00 00 00 00    lea    rcx,[rip+0x0]        # 10 <main+0x10>
  10:   ba 06 00 00 00          mov    edx,0x6
  15:   e8 00 00 00 00          call   1a <main+0x1a>
  1a:   31 c0                   xor    eax,eax
  1c:   48 83 c4 28             add    rsp,0x28
  20:   c3                      ret
  21:   90                      nop

So, gcc behandelt beides static const und #define wie das gleiche, wenn es optimieren.

  • Hast du mit aktivierten Optimierungen getestet?

    – Maisstängel

    21. November 2014 um 20:05 Uhr

  • @Cornstalks Ich hatte es mit ausgeschalteten Optimierungen getestet, es ergibt das gleiche.

    – Venkatesh

    21. November 2014 um 20:16 Uhr


  • Ich denke nicht, dass es sehr sinnvoll ist, diesen Test mit ausgeschalteten Optimierungen durchzuführen. Ich habe gerade mit kompiliert -O2 auf meiner Maschine und beide Programme erzeugen exakt dieselbe Versammlung. Also nein, ich denke wir können sagen Makro ist nicht unbedingt besser.

    – Maisstängel

    21. November 2014 um 22:37 Uhr

  • Diese Antwort ist nicht korrekt, siehe meine Antwort, beide Fälle sollten nach der Optimierung identisch sein.

    – Shafik Yaghmour

    23. November 2014 um 2:28 Uhr

  • Gut. Versuchen wir noch ein paar Mal, auf einen Präprozessor foo zu verweisen und über die Codegröße nachzudenken.

    – djacob

    7. Juni 2020 um 14:35 Uhr

Benutzer-Avatar
Shafik Yaghmur

Der schnelle Weg, um einfache Optimierungsfragen zu testen, ist die Verwendung von Godbolt.

Für Ihr spezielles Problem sollte ein moderner Optimierungscompiler in der Lage sein, für beide Fälle denselben Code zu erzeugen, und optimiert sie tatsächlich nur auf eine Konstante. Wir können dies mit dem folgenden Programm sehen (live sehen):

#include <stdio.h>

#define CONSTANT 6
static const int  constant = 6;

void func()
{
  printf( "%d\n", constant ) ;
  printf( "%d\n", CONSTANT ) ;
}

in beiden Fällen reduzieren sich beide Zugriffe auf Folgendes:

movl    $6, %esi    #,

Wenn die Definition der Konstante für die Übersetzung sichtbar ist, kann der Compiler dies sicherlich als Optimierung verwenden.

Dies würde eine Variable namens Konstante erstellen, die auf dem Stapel leben würde, was meiner Meinung nach mit etwas mehr Gepäck einhergehen würde als ein #define.

Es könnte an mehreren Orten „leben“. Ein Compiler kann die Konstante sicherlich dort ersetzen, wo darauf verwiesen wird, ohne dass statische oder Stack-Speicherung erforderlich ist.

Angenommen, ich brauche eine Konstante in einer Situation, in der ich wählen könnte, ob ich entweder einen Präprozessor #define oder eine statische const-Anweisung verwenden möchte, ohne dass es offensichtliche Gründe dafür gibt, eine der beiden zu wählen, was effizienter ist?

Es hängt vom Compiler und der Architektur ab. Ich habe den Eindruck, dass manche glauben #define hat einen großen Vorteil. Das tut es nicht. Der offensichtliche Fall ist eine komplexe Auswertung oder ein Funktionsaufruf (z sin(4.8). Stellen Sie sich eine Konstante vor, die in einer Schleife verwendet wird. Eine richtig definierte Konstante könnte einmal ausgewertet werden. Ein Define könnte jede Iteration auswerten.

Und wie genau würde ich vorgehen, um das selbst zu testen?

Lesen Sie die von jedem verwendeten Compiler erzeugte Assembly und messen Sie.

Wenn Sie eine Faustregel wollen, würde ich sagen: “Verwenden Sie eine Konstante, es sei denn #define liefert Ihnen eine messbare Verbesserung des Szenarios”.

Es gab eine gute Beschreibung in den GCC-Dokumenten darüber. Vielleicht erinnert sich jemand, wo genau das war.

Benutzer-Avatar
Johannes Bode

static const Variablen werden nicht auf dem Stack erstellt (sollten zumindest nicht erstellt werden); Platz für sie wird reserviert, wenn das Programm geladen wird, daher sollte mit ihrer Erstellung keine Laufzeiteinbuße verbunden sein.

Dort kann eine Laufzeitstrafe sein, die mit ihrer Initialisierung verbunden ist. obwohl die Version von gcc, die ich verwende, die Konstante zur Kompilierzeit initialisiert; Ich weiß nicht, wie verbreitet dieses Verhalten ist. Kommt es zu einer solchen Laufzeitstrafe, tritt diese nur einmalig beim Programmstart auf.

Darüber hinaus ist jeder Laufzeitleistungsunterschied zwischen einer statischen const-qualifiziertes Objekt und ein Literal1 (wozu ein Makro schließlich erweitert wird) sollte vernachlässigbar bis nicht vorhanden sein, abhängig vom Typ des Literals und der beteiligten Operation.

Blödes Beispiel (gcc version 4.1.2 20070115 (SUSE Linux)):

#include <stdio.h>

#define FOO_MACRO 5

static const int foo_const = 5;

int main( void )
{
  printf( "sizeof FOO_MACRO = %zu\n", sizeof FOO_MACRO );
  printf( "sizeof foo_const = %zu\n", sizeof foo_const );
  printf( "      &foo_const = %p\n",  ( void * ) &foo_const );

  printf( "FOO_MACRO = %d\n", FOO_MACRO );
  printf( "foo_const = %d\n", foo_const );

  return 0;
}

Ausgabe:

sizeof FOO_MACRO = 4
sizeof foo_const = 4
      &foo_const = 0x400660
FOO_MACRO = 5
foo_const = 5

Adresse von foo_const ist in dem .rodata Abschnitt der Binärdatei:

[[email protected]]~/prototypes/static: objdump -s -j .rodata static

static:     file format elf64-x86-64

Contents of section .rodata:
 40065c 01000200 05000000 73697a65 6f662046  ........sizeof F
                 ^^^^^^^^
 40066c 4f4f5f4d 4143524f 203d2025 7a750a00  OO_MACRO = %zu..
 40067c 73697a65 6f662066 6f6f5f63 6f6e7374  sizeof foo_const
 40068c 203d2025 7a750a00 20202020 20202666   = %zu..      &f
 40069c 6f6f5f63 6f6e7374 203d2025 700a0046  oo_const = %p..F
 4006ac 4f4f5f4d 4143524f 203d2025 640a0066  OO_MACRO = %d..f
 4006bc 6f6f5f63 6f6e7374 203d2025 640a00    oo_const = %d..

Beachten Sie, dass das Objekt bereits auf 5 initialisiert ist, sodass es keine Laufzeitinitialisierungsstrafe gibt.

In dem printf Anweisungen, die Anweisung, den Wert von zu laden foo_const hinein %esi erfordert ein Byte mehr als das, um den Literalwert zu laden 0x5und die Anweisung muss effektiv dereferenzieren %rip registrieren:

400538:       be 05 00 00 00          mov    $0x5,%esi
              ^^^^^^^^^^^^^^
40053d:       bf ab 06 40 00          mov    $0x4006ab,%edi
400542:       b8 00 00 00 00          mov    $0x0,%eax
400547:       e8 e4 fe ff ff          callq  400430 <[email protected]>
40054c:       8b 35 0e 01 00 00       mov    270(%rip),%esi        # 400660 <foo_const>
              ^^^^^^^^^^^^^^^^^
400552:       bf bb 06 40 00          mov    $0x4006bb,%edi
400557:       b8 00 00 00 00          mov    $0x0,%eax
40055c:       e8 cf fe ff ff          callq  400430 <[email protected]>

Wird sich dies in einem messbaren Laufzeitleistungsunterschied niederschlagen? Vielleicht, unter den richtigen Umständen. Wenn Sie mehrere hunderttausend Mal in einer engen Schleife etwas CPU-gebundenes tun, dann ja, indem Sie ein Makro (das sich in ein Literal auflöst) über a verwenden static const Variable kann messbar schneller sein. Wenn dies während der Lebensdauer des Programms einmal vorkommt, ist der Unterschied zu gering, um gemessen zu werden, und es gibt keinen zwingenden Grund, das Makro über dem zu verwenden static const Variable.

Wie immer zählen Korrektheit und Wartbarkeit mehr als Leistung2. Es ist weniger wahrscheinlich, dass Sie einen Fehler machen, wenn Sie a verwenden static const statt Makro. Betrachten Sie das folgende Szenario:

#define FOO 1+2
...
x = FOO * 3;

Welche Antwort würden Sie erwarten vonund was würden Sie antworten erhalten? Vergleichen Sie das mit

static const int foo = 1+2;
...
x = foo * 3;

Ja, Sie könnten den Makrofall beheben, indem Sie Klammern verwenden – (1 + 2). Der Punkt ist, dass dieses Szenario kein Problem darstellt, wenn Sie die verwenden static const Objekt. Es ist eine Möglichkeit weniger, sich selbst ins Knie zu schießen.


1. Im Moment spreche ich nur von einfachen Skalarliteralen (Ganzzahlen oder Gleitkommazahlen), nicht von zusammengesetzten Literalen; haben ihr Verhalten nicht untersucht.

2. Es spielt keine Rolle, wie schnell Ihr Code ist, wenn er Ihnen die falsche Antwort gibt oder das Falsche tut. Es spielt keine Rolle, wie schnell Ihr Code ist, wenn niemand ihn reparieren oder aktualisieren kann, weil er nicht versteht, wie er funktioniert. Es spielt keine Rolle, wie schnell Ihr Code ist, wenn er beim ersten Hinweis auf eine schlechte Eingabe stirbt. Es spielt keine Rolle, wie schnell Ihr Code ist, wenn er Malware Tür und Tor öffnet.

Benutzer-Avatar
Adam C

Sie haben Ihre Frage völlig geändert. Hier meine Antwort auf deine neue Frage:

Da wir über C sprechen und davon ausgehen, dass Sie das Array auf dem Stack deklarieren, ist die Antwort tatsächlich sehr interessant. In diesem Fall kann es keinen Unterschied zwischen den beiden geben. Die “6” wird eigentlich zur Laufzeit nicht verwendet! Da Sie es nur verwenden, um die Größe eines Arrays auf dem Stapel festzulegen, verwendet der Compiler dies einfach, um zu berechnen, wie viel Stapelplatz für die Variable erforderlich ist.

Angenommen, Sie haben einen 32-Bit-Adressraum und Ihre lokale Funktion enthält dieses 6-Byte-Array (myArray) und eine vorzeichenlose 32-Bit-Ganzzahl (myInt). Der Compiler erstellt die folgenden Anweisungen zum Eingeben dieser Funktion: – Schreibe die 4-Byte-Rücksprungadresse auf den Stapel – Bewege den Stapelzeiger um 10 Bytes vorwärts
Während der Ausführung der Funktion kennt die Laufzeitumgebung weder die Namen noch die Größen von Variablen. Wenn Ihr Code sagt

myInt = 5;
myArray[myInt] = 25;

dann hat der Compiler diese Anweisungen generiert:

- write 00000000 00000000 00000000 00000101 starting at address (StackPointer - 4)
- write 00001101 starting at (StackPointer - 10 + (value at Stackpointer - 4))

Sie sehen also, der Wert “6” wird zur Laufzeit nicht verwendet. Tatsächlich können Sie in Index 6, 7, 8 schreiben, was immer Sie wollen. Die Laufzeit weiß nicht, dass Sie das Ende des Arrays überlaufen. (Aber je nachdem, wie Sie den Code schreiben, kann der Compiler den Fehler zur Kompilierzeit abfangen.)

Ich habe dort einige Details beschönigt (zweifellos einige, die mir nicht einmal bewusst sind), aber das ist das Wesentliche. (Ich begrüße Ihre Kommentare)

Das Definieren der 6 als “Konstante” kann tatsächlich dazu führen, dass der Wert in 4 Bytes nutzlosen Speicherplatzes gespeichert wird, aber das hat keinen Einfluss auf die Ausführung. Offensichtlich wird es wegoptimiert, weil es nie verwendet wird.

Aber trotz alledem, machen Sie sich keine Sorgen darüber, ein Byte Platz zu sparen. Code Wartbarkeit ist viel wichtiger. Das Risiko, einen einzelnen winzigen Fehler einzuführen oder Ihren Code ein kleines bisschen weniger lesbar zu machen, diese Risiken sind eine Billion Billionen Mal teurer als die Kosten für ein paar zusätzliche Bytes oder einen zusätzlichen Prozessorzyklus. Verwenden Sie Konstanten und Aufzählungen, um alle hier aufgeführten Vorteile zu nutzen

1359060cookie-check„Static const“ vs. „#define“ für Effizienz in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy