Werden konstante C-Ausdrücke zur Kompilierzeit oder zur Laufzeit ausgewertet?

Lesezeit: 7 Minuten

Benutzer-Avatar
Richter Maygarden

Wenn ich a schreibe #definieren der eine Operation mit anderen Präprozessorkonstanten durchführt, wird der endgültige Wert jedes Mal berechnet, wenn das Makro zur Laufzeit erscheint? Hängt dies von Optimierungen im Compiler ab oder wird es von einem Standard abgedeckt?

Beispiel:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMERB_1_S / 10

Wird die Operation 32768 / 10 jedes Mal zur Laufzeit auftreten, wenn ich das Makro TIMER_100_MS verwende?

Folgendes möchte ich vermeiden:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                3276

Zusammenfassung

Ein Compiler ist erforderlich, um konstante Integralausdrücke auswerten zu können, da sie zum Berechnen von Dingen wie Array-Größen zur Kompilierzeit erforderlich sind. Der Standard sagt jedoch nur, dass sie dies “können” – nicht “müssen”. Daher würde nur ein hirntoter Compiler einen konstanten Integralausdruck zur Kompilierzeit nicht auswerten, aber eine einfache Überprüfung der Assembly-Ausgabe für einen unkonventionellen Compiler würde jeden Fall verifizieren.

  • beachten Sie, dass TIMERB_1_S / 10 ist 3276nicht 3277

    – MM

    4. Februar 2016 um 0:08 Uhr

Makros sind einfach Textersetzungen, also in Ihrem Beispielschreiben TIMER_100_MS in einem Programm ist eine schicke Art zu schreiben 32768 / 10.

Daher stellt sich die Frage, wann der Compiler auswerten würde 32768 / 10, was ein konstanter integraler Ausdruck ist. Ich glaube nicht, dass der Standard hier ein bestimmtes Verhalten erfordert (da Laufzeit- und Kompilierzeitauswertung nicht zu unterscheiden sind), aber jeder halbwegs anständige Compiler wird es zur Kompilierzeit auswerten.

  • Dies ist der entscheidende Punkt. Der Präprozessor manipuliert den Text, dann bekommt der Compiler ihn und weiß Bescheid nichts darüber, wie viel Vorverarbeitung vorher geflossen ist …

    – dmckee — Ex-Moderator-Kätzchen

    12. Januar 2009 um 18:16 Uhr

  • Selbst wenn der Präprozessor dies tun würde, kann er dies nicht tun, solange er nicht so definiert ist: #define TIMER_100_MS (TIMERB_1_S / 10) Da Sie den Ausdruck nicht in Klammern setzen, wäre es schwierig, ihn auszuwerten 3277 wo du vielleicht geschrieben hast 1 / TIMER_100_MS (Wo das Ergebnis sowieso nicht das wäre, was ich denke) Aber wenn der Präprozessor die Anweisung hier bereits ausgewertet hätte, würde er sogar die Prioritätsreihenfolge der Operatoren brechen.

    – dhein

    12. Februar 2015 um 12:18 Uhr

Benutzer-Avatar
Johannes Schaub – litb

Die meisten Antworten hier konzentrierten sich auf die Wirkung der Makrosubstitution. Aber ich glaube, er wollte wissen, ob

32768 / 10

wird zur Kompilierzeit ausgewertet. Das ist zunächst einmal ein arithmetischer Konstantenausdruck und zusätzlich ein ganzzahliger Konstantenausdruck (weil er nur Literale vom Typ Integer hat). Die Implementierung kann es zur Laufzeit berechnen, muss es aber auch zur Kompilierzeit berechnen können, weil

  1. es muss eine diagnostische Meldung ausgeben, wenn ein konstanter Ausdruck nicht in dem Typ darstellbar ist, den sein Ausdruck hat
  2. solche Ausdrücke sind in Kontexten zulässig, die den Wert zur Übersetzungszeit erfordern, beispielsweise wenn sie als Größe einer Array-Dimension verwendet werden.

Wenn der Compiler das Ergebnis prinzipiell bereits zur Kompilierzeit berechnen kann, sollte er diesen Wert verwenden und nicht zur Laufzeit neu berechnen, denke ich. Aber vielleicht gibt es noch einen Grund, das zu tun. Ich würde es nicht wissen.

Bearbeiten: Es tut mir leid, dass ich die Frage so beantwortet habe, als ob es um C++ ginge. Ich habe heute bemerkt, dass Sie nach C gefragt haben. Das Überlaufen in einem Ausdruck wird in C als undefiniertes Verhalten angesehen, unabhängig davon, ob es in einem konstanten Ausdruck auftritt oder nicht. Der zweite Punkt gilt natürlich auch in C.

Bearbeiten: Als Kommentar bemerkt, wenn das Makro in einen Ausdruck wie ersetzt wird 3 * TIMER_100_MSdann würde dies auswerten (3 * 32768) / 10. Daher lautet die einfache und direkte Antwort “Nein, es würde nicht jedes Mal zur Laufzeit auftreten, da die Division möglicherweise aufgrund von Vorrang- und Assoziativitätsregeln überhaupt nicht auftritt.”. Meine obige Antwort geht davon aus, dass das Makro immer so ersetzt wird, dass die Division tatsächlich stattfindet.

  • Genau genommen ist das falsch. Aufgrund der Makrosubstitution können Sie nicht wissen, was dies auswertet, da es darauf ankommt, was nach der Substitution daneben erscheint. Zum Beispiel, 3 * 32768 / 10 kann anders sein als 32768 / 10 * 3 in einem ganzzahligen Ausdruck aufgrund der Reihenfolge der Operationen von links nach rechts. Wenn die Makrodefinition den Ausdruck mit umgeben hat ()dann wäre die Antwort wahr.

    – Backstein

    13. Februar 2017 um 21:06 Uhr

  • @Brick, aber in diesem Fall handelt es sich nicht um eine “Operation 32768 / 10”. Die Frage bezog sich jedoch nur auf die “Operation 32768 / 10”. Meine Antwort sagt also nichts über den Fall “3 * 32768 / 10” aus, da es sich um “(3 * 32768) / 10” handelt. Meine Antwort gilt für “32768 / 10 * 3” und die anderen Fälle, in denen es eine solche Operation gibt.

    – Johannes Schaub – litb

    13. Februar 2017 um 21:24 Uhr


  • Dies ist die direkt kopierte Frage: “Wird die Operation 32768 / 10 jedes Mal zur Laufzeit ausgeführt, wenn ich das Makro TIMER_100_MS verwende?” Es geht definitiv um die Verwendung des Makros, und das Makro wird ohne die Klammer ausgewertet. Sie können nicht wissen, ob dies vereinfacht oder nicht, es sei denn, Sie kennen jeden Ort, an dem das Makro verwendet wird.

    – Backstein

    13. Februar 2017 um 21:26 Uhr

Mir ist kein Standard bekannt, der garantiert, dass es optimiert wird. Der Präprozessor ersetzt TIMER_100_MS durch 32768/10, was Sie sehen können, indem Sie gcc -c ausführen. Um zu sehen, ob der Compiler weiter optimiert, führen Sie gcc -S aus und überprüfen Sie den Assembler. Mit gcc 4.1 wird dies auch ohne Optimierungs-Flags während der Kompilierung auf die Konstante reduziert:

#include <stdlib.h>
#include <stdio.h>

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMER_1_S / 10

int main(int argc, char **argv)
{
  printf("%d\n", TIMER_100_MS);

  return(0);
}

gcc -S test.c
cat test.s

...
    popl    %ebx
    movl    $3276, 4(%esp)
    leal    LC0-"L00000000001$pb"(%ebx), %eax
    movl    %eax, (%esp)
    call    L_printf$stub
...

  • Mit dieser Technik können Sie zeigen, dass sogar komplizierte mathematische Operationen, wie z log(3.0) wird in direkte Inline-Werte übersetzt, obwohl die Disassemblierung kodiert log(3.0) wie 4607626529066517259 Dies ist die 64-Bit-Ganzzahlumwandlung des äquivalenten IEEE-Double …

    – Mark Lakata

    17. Mai 2016 um 23:55 Uhr

Der Compiler sollte diesen Ausdruck optimieren. Ich glaube nicht, dass es vom Standard verlangt wird, aber ich habe noch nie einen Compiler gesehen, der diese Aufgabe NICHT ausführen würde.

Sie sollten jedoch NICHT schreiben:

#define TIMER_100_MS      TIMERB_1_S / 10

… weil das ein Fehler ist, der darauf wartet, passiert zu werden. Sie sollten immer #defines mit Ausdrücken in Klammern setzen.

#define TIMER_100_MS      (TIMERB_1_S / 10)

In Betracht ziehen :

i = 10 * TIMER_100_MS;

Der erste Fall würde 32768 ((10 * TIMERB_1_S) / 10) ergeben, der zweite 32760 (10 * (TIMERB_1_S / 10)). Kein kritischer Unterschied hier, aber Sie MÜSSEN sich dessen bewusst sein!

Benutzer-Avatar
Richter Maygarden

Von dem Entwurf des Ausschusses WG14/N1124 – 6. Mai 2005 ISO/IEC 9899:TC2:

6.6 Konstante Ausdrücke

Syntax

konstanter Ausdruck:
bedingter Ausdruck

Beschreibung

Ein konstanter Ausdruck kann eher während der Übersetzung als zur Laufzeit ausgewertet werden und kann dementsprechend an jeder Stelle verwendet werden, an der sich eine Konstante befindet.

Einschränkungen

Konstante Ausdrücke dürfen keine Zuweisungs-, Inkrement-, Dekrement-, Funktionsaufruf- oder Kommaoperatoren enthalten, es sei denn, sie sind in einem Unterausdruck enthalten, der nicht ausgewertet wird.96)

Jeder konstante Ausdruck muss zu einer Konstante ausgewertet werden, die im Bereich darstellbarer Werte für ihren Typ liegt.

  • Vielen Dank für das Zitieren Ihrer Quelle, es ist eine wertvolle Ressource.

    – Enrico

    30. März 2016 um 19:26 Uhr

Benutzer-Avatar
Bill die Eidechse

Wird die Operation 32768 / 10 jedes Mal zur Laufzeit auftreten, wenn ich die TIMERB_100_MS Makro?

Jede Stelle in Ihrem Code, an der Sie verwenden TIMERB_100_MSes wird durch ersetzt 32768 / 10 durch den Präprozessor.

Ob dieser Ausdruck weiter optimiert wird (er wird als Konstante ausgewertet), hängt von Ihrem Compiler ab.

  • Vielen Dank für das Zitieren Ihrer Quelle, es ist eine wertvolle Ressource.

    – Enrico

    30. März 2016 um 19:26 Uhr

Benutzer-Avatar
Norman Ramsey

Leute, diese Transformation wird “Konstantes Falten” genannt und sogar die meisten Studenten-Compiler machen das. Solange Sie einen Compiler haben, der nicht von Ihnen oder Ihrem College-Mitbewohner gebaut wurde, und Sie eine statisch typisierte Sprache kompilieren, können Sie sich auch ohne eingeschaltete Optimierung darauf verlassen. Anders sieht es aus, wenn Sie es mit einer verrückten dynamischen Sprache zu tun haben, die die Bedeutung von ändern darf /.

  • Ständiges Falten! das habe ich gesucht.

    – Benutzer2387149

    4. Oktober 2016 um 21:06 Uhr

1384000cookie-checkWerden konstante C-Ausdrücke zur Kompilierzeit oder zur Laufzeit ausgewertet?

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

Privacy policy