Was wären C und C++ ohne Präprozessor?



  • Präprozessor schrieb:

    Meine Frage ist nun, warum?

    Der Präprozessor macht nur Textersetzung und ist daher nicht typsicher. In C++ braucht man ihn nur für sehr wenige Dinge, unter anderem für bedingte Übersetzung.

    Präprozessor schrieb:

    Der Präprozessor ist doch so betrachtet geradezu ein Geschenk, es wäre schlimm ohne ihn auskommen zu müssen.

    Ohne Präprozessor wäre die Sache sehr viel besser. Bei C++ fehlt im Grunde nur ein echtes Modulkonzept und ein Mechnismus für bedingte Übersetzung. Bei C fehlt im Grunde alles, da ist man auf den Präprozessor angewiesen. Aber das ist kein Grund ihn dort nicht auch zu entsorgen.



  • Abgesehen von den Moeglichkeiten der bedingten Kompilierung finde ich den PP eigentlich nur scheisse. #defines sind gefaehrlich und #includes nerven einfach nur.
    Und fuer die bed. Kompilierung koennte man locker ein eigenes System einfuehren.



  • this->that schrieb:

    #defines sind gefaehrlich

    C++ ist halt keine Teletubby-Sprache.
    Zeiger sind auch gefährlich.
    Get over it.


  • Mod

    hustbaer schrieb:

    this->that schrieb:

    #defines sind gefaehrlich

    C++ ist halt keine Teletubby-Sprache.
    Zeiger sind auch gefährlich.
    Get over it.

    Zeiger sind auf eine andere Art gefährlich als defines, das sollte dir klar sein. defines brechen alle sonstigen Spracheigenschaften, weil sie sämtliche Scopes verletzen. Pointer sind ein ganz legitimes Mittel der Sprache, welches sich absolut konsistent mit dem Rest der Sprache verhält. Man kann damit (wie mit praktisch allem) Laufzeitfehler verursachen, wenn man sie falsch benutzt. Mit defines kann man im Code selbst Fehler erzeugen.



  • zu empfehen ist hierbei besonders

    #define true false
    #define + -

    :p



  • SeppJ schrieb:

    hustbaer schrieb:

    this->that schrieb:

    #defines sind gefaehrlich

    C++ ist halt keine Teletubby-Sprache.
    Zeiger sind auch gefährlich.
    Get over it.

    Zeiger sind auf eine andere Art gefährlich als defines, das sollte dir klar sein. defines brechen alle sonstigen Spracheigenschaften, weil sie sämtliche Scopes verletzen. Pointer sind ein ganz legitimes Mittel der Sprache, welches sich absolut konsistent mit dem Rest der Sprache verhält. Man kann damit (wie mit praktisch allem) Laufzeitfehler verursachen, wenn man sie falsch benutzt. Mit defines kann man im Code selbst Fehler erzeugen.

    Ich weiss das Zeiger und #defines etwas anderes sind.
    Ich sehe nur nicht wie die eine Art von "man kann Mist bauen/muss wissen was man tut" sich von der anderen Art unterscheiden soll?

    Inwiefern sollen #defines da schlimmer sein?

    Versteh mich nicht falsch, ich hätte nichts dagegen wenn jmd. einen brauchbaren Ersatz für #defines entwickeln würde. Eine Metaprogrammiersprache mit der man noch viel mehr/noch viel eleganter Code erzeugen kann wäre eine ganz tolle Sache.

    Nur #defines ersatzlos zu streichen halte ich für den falschen Weg, weil sie bei bestimmten Dingen einfach unglaublich praktisch sind.

    Nämlich an den Stellen, wo man ohne #defines Code x-fach wiederholen müsste.

    ----

    Nochmal speziell dazu:

    defines brechen alle sonstigen Spracheigenschaften, weil sie sämtliche Scopes verletzen.

    #defines befinden sich ausserhalb der Spracheigenschaften auf die du dich beziehst, und können sie daher weder brechen noch Scopes verletzen, da es an der Stelle noch gar keine Scopes gibt.
    Ich verstehe nicht was daran schlimm sein soll. Es gibt ja gute Richtlinien die man befolgen kann um diesbezügliche Probleme zu vermeiden.



  • hustbaer schrieb:

    Nämlich an den Stellen, wo man ohne #defines Code x-fach wiederholen müsste.

    Nenn mal ein Beispiel was man nicht anders lösen könnte...



  • David W schrieb:

    Nenn mal ein Beispiel was man nicht anders lösen könnte...

    Include Guards.



  • Ein nettes Beispiel aus der VCL bzgl. '#define':

    #include <windows.h>
    
    class TCanvas
    {
    public:
      void TextOut(/*...*/);
    }
    

    Frage: Wie lautet der richtige Name der Methode (d.h. nachdem der Präprozessor diesen Code durchlaufen hat)?


  • Mod

    hustbaer schrieb:

    Ich sehe nur nicht wie die eine Art von "man kann Mist bauen/muss wissen was man tut" sich von der anderen Art unterscheiden soll?

    Das will ja auch niemand ersatzlos streichen.

    Nochmal speziell dazu:

    defines brechen alle sonstigen Spracheigenschaften, weil sie sämtliche Scopes verletzen.

    #defines befinden sich ausserhalb der Spracheigenschaften auf die du dich beziehst, und können sie daher weder brechen noch Scopes verletzen, da es an der Stelle noch gar keine Scopes gibt.

    Und eben das ist doch das Problem! Es ist ein Sprachelement, das sich völlig anders als der Rest der Sprache verhält. Ich hkann ein define vor deinen Code setzen und diesen (versehentlich oder absichtlich) dadurch vollkommen zerstören. Mit anderen Sprachmitteln kann mir das nicht so leicht versehentlich passieren, bzw. ich bekomme eine aussagekräftige Fehlermeldung, weil der Compiler das Problem analysieren kann. Sofern dein Code namespaces benutzt, müsste ich diese schon absichtlich missachten, um deinen Code kaputt zu machen. Wie oft kommen Fragen im Forum, weil irgendein dummer Header ein Makro namens min/max definiert hat? Bestimmt alle 1-2 Monate.

    Guter Ersatz für Präprozessordefines wäre zum Beispiel eine Formalisierung der jetzt schon vorhandenen pragmas. Ja, die gehören auch zum Präprozessor, sind aber weitaus weniger problematisch - ihr Problem ist die mangelnde Portabilität. Ein pragma once kann Includeguards ersetzen und man könnte auch ein Pragma für bedingte Compilierung einführen. Und damit hat man (zumindest für C++) schon einmal 99% der Präprozessoranweisungen ersetzt. Mit ein bisschen Nachdenken (nich unbedingt spontan um kurz vor 12 in der Nacht wie jetzt) kann man den Rest sicherlich auch noch gut ersetzen.



  • David W schrieb:

    hustbaer schrieb:

    Nämlich an den Stellen, wo man ohne #defines Code x-fach wiederholen müsste.

    Nenn mal ein Beispiel was man nicht anders lösen könnte...

    Mit momentanen Mitteln kann man vieles nicht ohne Makros lösen. Bzw. es ist möglich, aber nicht vernünftig oder man muss enorm viel Boilerplate-Code in Kauf nehmen. Was das anbelangt, sind Makros an manchen Orten unschlagbar, wo Compile- und Laufzeit-Abstraktionsmechanismen versagen. Konkret fallen mir neben den bereits genannten Anwendungsfällen z.B. Debug- und Log-Funktionen mit __LINE__ , __FILE__ und Stringize-Operator #, oder Lesbar-Machungen von Template-Traits und Dingen wie STATIC_ASSERT ein. Zumindest bei letzterem sehe ich auch nicht gerade, wie man das verbessern könnte...

    SeppJ schrieb:

    Sofern dein Code namespaces benutzt, müsste ich diese schon absichtlich missachten, um deinen Code kaputt zu machen. Wie oft kommen Fragen im Forum, weil irgendein dummer Header ein Makro namens min/max definiert hat? Bestimmt alle 1-2 Monate.

    Richtig, aber auch beim Präprozessor gibt es gewisse Regeln, mit deren Einhaltung man viel weniger Mist baut. Eine lautet z.B. "wähle keine Allerweltsnamen". Man kann genauso argumentieren, dass man absichtlich derart idiotische Makro-Bezeichner wählen muss, um Code zu zerstören. Aber es stimmt natürlich, dass die Auswirkungen bei Makros noch verheerender sind, weil man sie in einem anderen Scope nicht einfach verdecken kann.

    Ich sehe das Riesenproblem des Präprozessors ehrlich gesagt nicht. Besonders in C++ ist man ohnehin eher selten auf ihn angewiesen (von Includes und Header-Guards mal abgesehen). Makros sind sicher nicht der Weisheit letzter Schluss, aber manchmal sind sie eben doch verdammt praktisch. Ich weiss nicht, ob es klüger wäre, stattdessen etliche andere Sprachmittel einzubauen, und dabei immer noch einzelne Fälle ungelöst lassen.



  • SeppJ schrieb:

    defines brechen alle sonstigen Spracheigenschaften, weil sie sämtliche Scopes verletzen.

    #defines befinden sich ausserhalb der Spracheigenschaften auf die du dich beziehst, und können sie daher weder brechen noch Scopes verletzen, da es an der Stelle noch gar keine Scopes gibt.

    Und eben das ist doch das Problem! Es ist ein Sprachelement, das sich völlig anders als der Rest der Sprache verhält.

    Wieso soll das ein Problem sein?

    Ich hkann ein define vor deinen Code setzen und diesen (versehentlich oder absichtlich) dadurch vollkommen zerstören.

    Versehentlich kann dir das nur passieren, wenn du dich an keinerlei etablierte Regeln hältst oder gigantisch viel Pech hast. Und absichtlich kann man immer alles kaputtbekommen.

    Mit anderen Sprachmitteln kann mir das nicht so leicht versehentlich passieren, bzw. ich bekomme eine aussagekräftige Fehlermeldung, weil der Compiler das Problem analysieren kann.

    Ja, klar, bei sämtlichen anderen Dingen bekommt man ne aussagekräftige Fehlermeldung. Sorry, aber das ist nur mehr Quark. Sag mir mal wo du ne aussagekräftige Fehlermeldung herbekommen willst wenn du z.B. versehentlich nen Overload machst der das Programmverhalten in unerwünschter Form verändert.

    Sofern dein Code namespaces benutzt, müsste ich diese schon absichtlich missachten, um deinen Code kaputt zu machen.

    Und wenn mein Code entsprechende Prefixe für Makronamen verwendet (was er tut), müsstest du ebenso mutwillig Unfug anrichten wollen.

    Wie oft kommen Fragen im Forum, weil irgendein dummer Header ein Makro namens min/max definiert hat? Bestimmt alle 1-2 Monate.

    OK. Eine (grosse) Firma hat mit Makros Mist gebaut. Klar, das ist natürlich das Argument dafür dass das Feature vollkommen unbrauchbar weil viel zu gefährlich ist.

    Guter Ersatz für Präprozessordefines wäre zum Beispiel eine Formalisierung der jetzt schon vorhandenen pragmas. Ein pragma once kann Includeguards ersetzen und man könnte auch ein Pragma für bedingte Compilierung einführen. Und damit hat man (zumindest für C++) schon einmal 99% der Präprozessoranweisungen ersetzt. Mit ein bisschen Nachdenken (nich unbedingt spontan um kurz vor 12 in der Nacht wie jetzt) kann man den Rest sicherlich auch noch gut ersetzen.

    Ja, klar, wenn du den PP nie für was anderes als #ifdef _DEBUG und assert() verwendest mag das für dich so aussehen.

    Guck dir doch bitte einfach mal an was BOOST_FOREACH bzw. die Boost.Log Makros machen.



  • Defines sind doch der einzige weg, wenn man unterschiedliche OS APIs im selben Code unterstützen will.


  • Mod

    hustbaer schrieb:

    Versehentlich kann dir das nur passieren, wenn du dich an keinerlei etablierte Regeln hältst oder gigantisch viel Pech hast.

    Man muss sich also an Regeln halten, weil es gefährlich ist.

    Ja, klar, wenn du den PP nie für was anderes als #ifdef _DEBUG und assert() verwendest mag das für dich so aussehen.

    Guck dir doch bitte einfach mal an was BOOST_FOREACH bzw. die Boost.Log Makros machen.

    Tu nicht so, als würde ich diese Anwendungen nicht kennen. Dann tu ich auch nicht so, als würde dir dazu ein globales define als einzige Möglichkeit einfallen, das gleiche zu erreichen.



  • kjhgfas schrieb:

    Defines sind doch der einzige weg, wenn man unterschiedliche OS APIs im selben Code unterstützen will.

    Nein, man kann sich auch Buildfiles zusammenhacken.



  • Nick Unbekannt schrieb:

    David W schrieb:

    Nenn mal ein Beispiel was man nicht anders lösen könnte...

    Include Guards.

    Wir redeten von den defines, nicht den includes (wobei die auch ziemlich nervig sind, sprach SeppJ auch schon an.

    Nexus schrieb:

    Debug- und Log-Funktionen mit __LINE__, __FILE__

    Kann man auch in statischen klassen auslagern, ein TraceStack und done, muss nur noch entwickelt werden.

    Nexus schrieb:

    STATIC_ASSERT

    Gibts in anderen Sprachen schon lange, zb
    Contract.Requires(i == 5, "The parameter has to be 5");

    ich persönlich habe nichts gegen den Präprozessoren im allgemeinen, nur die defines sollten komplett ersetzt werden, das erzeugt schnell unübersichtlichen Code.
    -> Sauberkeit
    -> Übersicht
    -> Maintenance



  • otze schrieb:

    kjhgfas schrieb:

    Defines sind doch der einzige weg, wenn man unterschiedliche OS APIs im selben Code unterstützen will.

    Nein, man kann sich auch Buildfiles zusammenhacken.

    Dann hast du aber nicht mehr den selben Code sondern zwei Files die teilweise den gleichen und teilweise unterschiedlichen OS abhängigen Code enthalten.



  • David W schrieb:

    Wir redeten von den defines, nicht den includes (wobei die auch ziemlich nervig sind, sprach SeppJ auch schon an.

    Und wenn man diese mit defines schützt, sind sie alles andere als nervig. Auch kann man erst dann von portablen include Guards reden. pragma once ist zwar eine Möglichkeit, aber nicht portabel. Und das auch nicht grundlos, weil die bedingte Compilierung, die bei den include guards genutzt wird, sind wesentlich mächtiger. Ich finde implizite includes, wie zum Beispiel in Java, wesentlich verwirrender und auch fehleranfälliger.

    David W schrieb:

    Kann man auch in statischen klassen auslagern, ein TraceStack und done, muss nur noch entwickelt werden.

    Gibts aber nicht. Und das auch aus nachvollziehbaren Grund. Du müsstest so Klassen entwickeln, die weit mehr über ihren Kontext wissen müssen, als eine Sprache wie C++ ihnen zugesteht. Diese fehlenden Laufzeitinformationen, sind meiner Meinung nach auch ein riesen Vorteil von C++, gegenüber anderen Sprachen.

    David W schrieb:

    Gibts in anderen Sprachen schon lange,

    Wäre wieder eine Einmischung in den Code, die dort nichts verloren hat. Wenn man so etwas braucht, wäre eine Überarbeitung des Präprozessors sicher die bessere Wahl. Wobei ich davon auch nichts halte, weil in seiner jetzigen Form bietet der Präprozessor auch noch Verwendungsmöglichkeiten, ohne Compiler.



  • kjhgfas schrieb:

    otze schrieb:

    kjhgfas schrieb:

    Defines sind doch der einzige weg, wenn man unterschiedliche OS APIs im selben Code unterstützen will.

    Nein, man kann sich auch Buildfiles zusammenhacken.

    Dann hast du aber nicht mehr den selben Code sondern zwei Files die teilweise den gleichen und teilweise unterschiedlichen OS abhängigen Code enthalten.

    Das sollte man sowieso machen. Für die OS-spezifischen sachen eigene dateien zu verwenden ist das absolute minimum. Klar gibt es spezies, die alles in eine datei hacken *grusel*.

    Der allgemeine teil kommt in eine allgemeine implementierung, OS-spezifische sachen in jeweils eine eigene datei. Und das selbstverständlich auch, wenn ich den PP dafür benutze. Alles andere ist ein scheiss hack, welcher bei jeder portierung erneut angepasst werden muss.



  • SeppJ schrieb:

    hustbaer schrieb:

    Versehentlich kann dir das nur passieren, wenn du dich an keinerlei etablierte Regeln hältst oder gigantisch viel Pech hast.

    Man muss sich also an Regeln halten, weil es gefährlich ist.

    Jetzt sind wir wieder ganz am Anfang: das Leben ist gefährlich. Get over it. 🙄

    Ja, klar, wenn du den PP nie für was anderes als #ifdef _DEBUG und assert() verwendest mag das für dich so aussehen.

    Guck dir doch bitte einfach mal an was BOOST_FOREACH bzw. die Boost.Log Makros machen.

    Tu nicht so, als würde ich diese Anwendungen nicht kennen. Dann tu ich auch nicht so, als würde dir dazu ein globales define als einzige Möglichkeit einfallen, das gleiche zu erreichen.

    DU tust doch so als würdest du diese Anwendungen nicht kennen. Entweder das, oder du unterschätzt die Aufgabenstellung gewaltig, einen Ersatz für den Präprozessor zu definieren, mit dem solche Dinge nach wie vor möglich sind. (Und der dann auch noch weniger "gefährlich" ist).

    Entweder du kastrierst die Sprache, oder du erlaubst Dinge die "gefährlich" sind. Guck dir bloss mal Reflection in C# oder Java an. Ist auch "gefährlich", dafür kann man einiges damit machen.


Anmelden zum Antworten