Präprozessor
-
Hallo liebe C/C++ Gemeinde
Ich hab mal wieder eine Verständnisfrage. Bei meinen Recherchen bin ich jetzt mehrfach darauf gestoßen, dass einige Möglichkeiten des Präprozessor (vor allem Makros) nur noch in sehr seltenen Fällen verwendet (mit Ausnahme von #include) werden sollte. Hier in der PDF (Link: http://www.j3l7h.de/lectures/1213ws/Informatik_1/Skripte/04_Praeprozessor,_Compiler,_Linker.pdf) wurde geschrieben, anstatt
#define KONSTANTE
wert sollte static const Datentyp = wert; verwendet werden. Und anstatt Makros nutzt man besser inline Funktionen.Warum wird der Präprozessor "so schlecht, bzw. Makros als verpönt" dargestellt? Meine Vermutung ist, dass man mit der static const Datentyp = wert; eine gewisse Typsicherheit hat. Kann man das so sagen?
In vielen Standardbibliotheken findet man ja nur #defines und viele Makros. Ich denke, dass ist der Entstehungszeit mit dem ANSI-C Standard geschuldet, oder?Wie haltet ihr das, nutzt ihr überwiegend inline-Funktionen und keine #defines mehr in euren Programmen? Bzw. tritt der "seltene" Fall ein, dass man doch noch Makros nehmen sollte?
Danke für eure Antworten.
-
Das ist tatsächlich richtig und hat auch die Gründe, die du vermutest. Der Präprozessor ist eine reine Textersetzung, die vor dem Compiliervorgang durchgeführt wird. Probleme, die dabei auftreten können:
-Der Präprozessor hat (fast) keine Ahnung von der Sprache C. Das einzige was er respektiert sind Zeichenkettenliterale. Ansonsten gibt es kaum Möglichkeiten zu beschränken, was wo wie ersetzt wird. Wenn man unwissend irgendwo im Programm einen Bezeichner wählt, der von einem wer weiß wo definierten Makro ersetzt wird hat man eben Pech. Beliebtes Beispiel sind min und max, die in der Winapi definiert werden und dadurch beispielsweise dafür sorgen, dass man selber nicht diese naheliegenden Bezeichner benutzen kann, wenn irgendwo die Winapi eingebunden wird (Es gibt zwar theoretisch eine Möglichkeit, das abzuschalten, ist aber umständlich).
-Der Präprozessor ist auch nicht typsicher, was sowohl gut als auch schlecht sein kann.
-Da der Präprozessor vor dem Compiler läuft, sind (compilergenerierte) Fehlermeldungen oft ziemlich irreführend, da der Compiler schließlich nur den Code sieht, den der Präprozessor erzeugt hat, der Programmierer aber normalerweise den Code vorliegen hat, wie er vor dem Präprozessor aussieht.
-Makros haben einige Fallstricke, sowohl für den Schreiber derselben, als auch für den Anwender. Der Schreiber sollte besser darauf achten, dass er um alle Parameter ordentlich Klammern setzt. Der Aufrufer muss beachten, dass eventuelle Nebeneffekte der Argumente gegebenenfalls mehrmals auftreten können. Daher besser keine Nebeneffekte in den Argumenten eines Makros nutzen. Aber was, wenn man gar nicht weiß, ob etwas ein Makro oder eine Funktion ist?
-Theoretisch kann ein Makro einfach undefiniert werden. Wenn irgendjemand versehentlich oder böswillig ein Makro, auf das du dich verlässt, in irgendeinem tief versteckten Header umdefiniert, dann viel Spaß beim Finden des Fehlers.In vielen Standardbibliotheken findet man ja nur #defines und viele Makros. Ich denke, dass ist der Entstehungszeit mit dem ANSI-C Standard geschuldet, oder?
Wovon genau redest du? Es gibt nur eine C Standardbibliothek. Ob andere, oft eingesetzte Bibliotheken gut gemacht sind oder nicht, musst du separat diskutieren. Es gibt auch manche Sachen, die gehen eben nur als Makro. Und manche Dinge, die gingen nur als Makro. Dein Beispiel mit den Konstanten ist so ein Fall, das geht eben erst ab C99 mit const als echte Compilezeitkonstante, davor musste man eben defines oder enums nutzen.
Wie haltet ihr das, nutzt ihr überwiegend inline-Funktionen und keine #defines mehr in euren Programmen?
Ehrlich gesagt programmiere ich keine ernsthaften Programme in C. Aber wenn ich es täte, dann würde ich Makros aus den genannten Gründen meiden.
-
Danke, für deine ausführliche Antwort!
Theoretisch kann ein Makro einfach undefiniert werden. Wenn irgendjemand versehentlich oder böswillig ein Makro, auf das du dich verlässt, in irgendeinem tief versteckten Header umdefiniert, dann viel Spaß beim Finden des Fehlers.
Wenn ich das richtig verstanden habe, meinst du z.B.: ich definiere in meiner C-Sourcecode Datei ein Makro mit dem Namen MAKEWORD(x, y) ... Wenn ich jetzt Windef.h einfüge, gibt es dort auch ein Makro mit dem gleichen Namen von Windows. Welches Makro würde der Präprozessor nutzen? Gibt es da eine Rangfolge? Theoretisch steht das Makro aus der Windef.h vor dem eigens definierten Makro, da ja die #include <windef.h> am Anfang der Datei ersetzt wurde.
Und wenn ich #undef Makroname nutze, ist es da egal ob das Makro vor oder nach der Zeile definiert wurde?
Wovon genau redest du? Es gibt nur eine C Standardbibliothek.
Ja, die meinte ich, hab mich da ein bisschen umständlich ausgedrückt. Ich meinte eig. die einzelnen Header-Files der C-Standardbibliothek, welche nur #defines nutzen.
Dein Beispiel mit den Konstanten ist so ein Fall, das geht eben erst ab C99 mit const als echte Compilezeitkonstante, davor musste man eben defines oder enums nutzen.
Wobei enums doch keine Präprozessordirektive ist, sondern vom Compiler verarbeitet werden, oder? Sollte man enums auch nicht mehr nutzen, oder habe ich dich da falsch verstanden?
Ehrlich gesagt programmiere ich keine ernsthaften Programme in C. Aber wenn ich es täte, dann würde ich Makros aus den genannten Gründen meiden.
Davon ernsthafte Programme zu schreiben, bin ich noch weit entfernt.
Aber C finde ich sehr interessant und mir macht das viel mehr Spaß als z.B. in Java zu programmieren.
-
Makro schrieb:
MAKEWORD(x, y) ... Wenn ich jetzt Windef.h einfüge, gibt es dort auch ein Makro mit dem gleichen Namen von Windows. Welches Makro würde der Präprozessor nutzen? Gibt es da eine Rangfolge? Theoretisch steht das Makro aus der Windef.h vor dem eigens definierten Makro, da ja die #include <windef.h> am Anfang der Datei ersetzt wurde.
Und wenn ich #undef Makroname nutze, ist es da egal ob das Makro vor oder nach der Zeile definiert wurde?
Klingt danach, als ob du dir wirklich Gedanken machst.
Nein, der C Standard schreibt nicht vor, welche von mehreren Quelldateien inkl. ihrer Header (und damit der Präpozessor-Lauf) zuerst abgearbeitet wird.
Du kannst darauf hoffen, dass der Compiler/Präpozessor bei doppelten Namen warnt, muss er aber nicht.
Das #undef kannst du dir sparen, der Präprozessor/Compiler nimmt immer die 'aktuelleste', und welches die aktuellste Variante ist, ist eben unbestimmt.Also am besten keine eigenen Makros verwenden, und wenn doch dann wenigstens welche mit definitiv (global gesehen) eigenen Namen.
Praktisch sind auch, wie du schon gemerkt hast, eigentlich nicht die Makros des Standards dein Problem, sondern die deiner zusätzlichen Bibliothek, die oftmals vielfach größer als die der Standardbibliothek sind (und damit größeres Konfliktpotential bieten).Kandidaten sind z.B. solche Laien-Versuche wie
#define MAX(a,b) ((a)>(b)?(a):(b)) oder gar #define max(a,b) ((a)>(b)?(a):(b))
Also nochmal, besser gar keine Makros benutzen und auch wenig/gar keine eigenen #define.
Gegenüber Funktionen hast du bei Makros auch den Nachteil, dass die Kopiersemantik der Parameter verloren geht, auch haben Makros keinen beeinflussbaren Scope (eben weil der Präprozessorlauf unabhängig vom eigentlichen Compiler vorher ausgeführt wird), von den exzessiv notwendigen Klammern bei Makros ganz zu schweigen.
-
Hallo
Danke für deine Antwort. In welcher Reihenfolge der Präprozessor die Quelldateien verarbeitet, hatte ich auch schon überlegt. Damit sind gleich zwei Fragen beantwortet.
Dann werde ich mal meine Makros durch inline-Funktionen ersetzen :D.
-
Lass das inline weg, ist sowieso nur eine Empfehlung an den Compiler, der kann eine inline-Funktion draus machen, muss er aber nicht.
Der Compiler braucht von dir als Entwickler keine Empfehlungen für Optimierungen, er weiß sowieso alles besser, ähnlich wie bei register Variablen.
-
Achso, dann lass ich inline weg.
Mir wird erst jetzt richtig klar, wie komplex Compiler doch eig. sind.
-
Makro schrieb:
Achso, dann lass ich inline weg.
Dann aber auch in einer Sourcedatei definieren!
-
Nathan schrieb:
Makro schrieb:
Achso, dann lass ich inline weg.
Dann aber auch in einer Sourcedatei definieren!
Und dann sind wir wieder bei dem Punkt, an dem es wahrscheinlich nicht mehr optimiert wird. Also doch wieder inline im Header für Minifunktionen.
-
SeppJ schrieb:
Und dann sind wir wieder bei dem Punkt, an dem es wahrscheinlich nicht mehr optimiert wird. Also doch wieder inline im Header für Minifunktionen.
Vielleicht können Compiler und Linker auch whole program optimization. Dann muss man sich darüber auch keine Sorgen mehr machen.
MSVC kann es.
GCC kann es.
-
So, ich bis nochmal
Ich hab weiterprobiert und recherchiert und mir ist noch folgendes Problem zu der Verwendung von Konstanten ein. Wenn ich diese im Header als
static const NAME = wert;
definiere, dann iststatic der storage class specifier
(Übersetzung - C-Speicherklasse ?) undconst der type qualifier
(Übersetzung - Typmodifizierer ?).Binde ich jetzt den Header (mit z.B. 100 solcher Konstanten ein), werden doch theoretisch alle Konstanten in das entsprechende Programmsegment gelegt. Laut diesem Beitrag:
How they are stored is an implementation detail (depends on the compiler).
For example, in the GCC compiler, on most machines, read-only variables, constants, and jump tables are placed in the text section.
http://stackoverflow.com/questions/1576489/where-are-constant-variables-stored-in-c ist nicht genau definiert, wo diese gespeichert werden.
Jetzt zu meiner eigentlichen Frage: Ist das speichertechnisch nicht eine Verschwendung so viele Konstanten (z.B. 100) zu speichern und ich verwende im Code dann z.B. nur 3 davon? Oder wird das seitens des Compilers optimiert? Oder habe ich da einen generellen Denkfehler drin?
Jetzt noch zur inline expansion:
Bei dieser ist es wichtig, dass der Compiler zur Compiltime die Funktionsdefinition der Funktion vorliegen hat (ist ja i-wie logisch, ansonsten kann der Compiler ja nicht entscheiden, ob die Funkion ersetzt oder aufgerufen wird). Das erreiche ich, indem ich die Funktion direkt in der entsprechenden Datei definiere. Dann muss ich kein inline hinzufügen, oder? Wenn ich die inline Funktion im Header definiere, ist das inline Pflicht?
-
Makro schrieb:
Jetzt zu meiner eigentlichen Frage: Ist das speichertechnisch nicht eine Verschwendung so viele Konstanten (z.B. 100) zu speichern und ich verwende im Code dann z.B. nur 3 davon? Oder wird das seitens des Compilers optimiert? Oder habe ich da einen generellen Denkfehler drin?
Die Antwort, dass das ein Implementierungsdetails ist, ist schon richtig, wenn auch nicht nützlich. In vielen Fällen wird die Konstante aber wohl überhaupt keine Repräsentation mehr im Speicher haben, sondern direkt im Programmcode eingesetzt werden. Das ist ja gerade der Sinn der Sache.
Bei dieser ist es wichtig, dass der Compiler zur Compiltime die Funktionsdefinition der Funktion vorliegen hat (ist ja i-wie logisch, ansonsten kann der Compiler ja nicht entscheiden, ob die Funkion ersetzt oder aufgerufen wird). Das erreiche ich, indem ich die Funktion direkt in der entsprechenden Datei definiere. Dann muss ich kein inline hinzufügen, oder? Wenn ich die inline Funktion im Header definiere, ist das inline Pflicht?
Und wie willst du Funktionen im Header ohne inline definieren? Als static? Der Header kann schließlich von mehreren Übersetzungseinheiten eingebunden werden und du darfst nur eine Definition einer Funktion im Gesamtprogramm haben. Ausnahmen sind eben static-Funktionen (dann hast du in jeder Übersetzungseinheit eine separate Funktion, von der die anderen Einheiten nichts mit bekommen. Die Funktionen dürfen sich sogar unterscheiden) oder inline. Dies ist der Hauptzweck von inline.
-
Hi
Die Antwort, dass das ein Implementierungsdetails ist, ist schon richtig, wenn auch nicht nützlich. In vielen Fällen wird die Konstante aber wohl überhaupt keine Repräsentation mehr im Speicher haben, sondern direkt im Programmcode eingesetzt werden. Das ist ja gerade der Sinn der Sache.
Hmm, wenn der Header eingebunden wird, sind die static const Variablen doch aber erstmal normale Variablen oder? Die Konstanten, die ich verwende, können ja direkt ersetzt werden, aber was passiert mit den Konstanten, die ich ja als "normale" Variablen deklariere und initialisiere und nicht verwende? (Falls ich zu unverständlich schreibe, bitte sagen :))
-
Die Konstanten, die ich verwende, können ja direkt ersetzt werden, aber was passiert mit den Konstanten, die ich ja als "normale" Variablen deklariere und initialisiere und nicht verwende?
Da die Konstante static ist, weiß der Compiler ja, dass sie nirgends außerhalb der Übersetzungseinheit benutzt wird und braucht daher auch keinen Speicherplatz für die Konstante zu erzeugen, außer es ist aus irgendeinem Grund für die aktuelle Übersetzungseinheit nötig (etwa weil du die Adresse der Konstante benutzt). Das ist natürlich keine Garantie, dass er das nicht trotzdem tut, Implementierungsdetail eben. Aber es wäre komisch, denn der Grund, wieso es solche Konstanten gibt, ist, dass man dann eben diese Art von Optimierung durchführen kann.
Hmm, wenn der Header eingebunden wird, sind die static const Variablen doch aber erstmal normale Variablen oder?
Wir reden hier schon von C99, oder? Ich bin den ganzen Thread über davon ausgegangen, da erst ab C99 const-Variablen überhaupt richtige Compilezeitkonstanten sind. Wenn wir von C89 reden, dann kannst du den gesamten Thread vergessen.
-
Da die Konstante static ist, weiß der Compiler ja, dass sie nirgends außerhalb der Übersetzungseinheit benutzt wird und braucht daher auch keinen Speicherplatz für die Konstante zu erzeugen, außer es ist aus irgendeinem Grund für die aktuelle Übersetzungseinheit nötig (etwa weil du die Adresse der Konstante benutzt). Das ist natürlich keine Garantie, dass er das nicht trotzdem tut, Implementierungsdetail eben. Aber es wäre komisch, denn der Grund, wieso es solche Konstanten gibt, ist, dass man dann eben diese Art von Optimierung durchführen kann.
Alles klar, damit ist meine Frage beantwortet, danke dir :).
Wir reden hier schon von C99, oder? Ich bin den ganzen Thread über davon ausgegangen, da erst ab C99 const-Variablen überhaupt richtige Compilezeitkonstanten sind. Wenn wir von C89 reden, dann kannst du den gesamten Thread vergessen.
Jo, wir reden hier von C99.
-
Ich bin immer wieder fasziniert was die Leute hier so an Wissen mitbringen
@SeppJ , @hustbear und so:)Respekt:)
-
SeppJ schrieb:
da erst ab C99 const-Variablen überhaupt richtige Compilezeitkonstanten sind.
? Zeig mal, wo das steht.
const bleibt const in C, egal ob C89 oder C99, Compilezeitkonstanten sind sie (im Gegensatz zu C++) beide Male nicht.
Die einzigen Unterschiede, die mir zw. C89 und C99 gerade einfallen sind VLA-Dimensionen und Initialisierer bei static Objekten.const int i = 10; int a[i]; /* funktioniert nur in C99, heißt aber noch lange nicht, dass i eine Konstante zur Compilezeit ist. */ const double pi = 4 * atan(1); /* ist konform in C89 und C99, weil pi eben keine Compilezeit-Konstante ist, da sie erst zur Laufzeit initialisiert wird/werden kann, und erst danach dann (also zur Laufzeit) unveränderlich/konstant ist. */
-
Wutz schrieb:
SeppJ schrieb:
da erst ab C99 const-Variablen überhaupt richtige Compilezeitkonstanten sind.
? Zeig mal, wo das steht.
const bleibt const in C, egal ob C89 oder C99, Compilezeitkonstanten sind sie (im Gegensatz zu C++) beide Male nicht.
Die einzigen Unterschiede, die mir zw. C89 und C99 gerade einfallen sind VLA-Dimensionen und Initialisierer bei static Objekten.Tatsächlich. Da bin ich wohl selber gewolft worden. Man sieht meine Aussage sehr oft in verschiedenen Quellen, die aber offenbar wohl alle eher mit Vorsicht zu genießen sind. Aber im Standard ist es tatsächlich nicht erlaubt und meine soeben gemachten Tests (ich habe das vorher nie wirklich geprüft) schlagen auch tatsächlich fehl.
-
Bedeutet das jetzt, dass diese Art der Optimierung:
Da die Konstante static ist, weiß der Compiler ja, dass sie nirgends außerhalb der Übersetzungseinheit benutzt wird und braucht daher auch keinen Speicherplatz für die Konstante zu erzeugen, außer es ist aus irgendeinem Grund für die aktuelle Übersetzungseinheit nötig (etwa weil du die Adresse der Konstante benutzt). Das ist natürlich keine Garantie, dass er das nicht trotzdem tut, Implementierungsdetail eben. Aber es wäre komisch, denn der Grund, wieso es solche Konstanten gibt, ist, dass man dann eben diese Art von Optimierung durchführen kann.
nicht durchgeführt wird? Oder hat das erstmal primär mit static zu tun (hab das in deinem Text nicht ganz rauslesen können, ob du hier als Konstante eine Variable meinst, die auf jeden Fall "const" als Qualifier haben muss) und nicht mit const, für das ich als Definition immer nur read-only gefunden habe (http://publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html), was nicht heißt, dass man das nicht umgehen kann.
-
Doch, diese Optimierung kann immer noch durchgeführt werden. Und ja, das static spielt dabei eine wichtige Rolle. Es würde sogar ohne das const gehen, denn der Compiler kann schließlich sehen, dass die Variable nirgendwo verändert wird.