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



  • 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.



  • hustbaer schrieb:

    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. 🙄

    Genau schmeißt die C++ Versicherung Concept und Module aus dem übernächsten Standard auch raus, wir haben unsere Werkzeuge - Get over it!



  • Die Compiler (Parser) könnten auch um einiges schneller sein, wenn es den PP nicht in diesem Umfang gäbe.
    Und auch die IDE-Unterstützung wäre dann präziser (was manchmal für ein Mist bei IntelliSense bzw. sogar Visual Assist rauskommt...).

    C# hat hier (meiner Meinung nach 😉 einen guten Kompromiss gefunden (nur bedingte Kompilierung ist mittels #define möglich - keine Textersetzung).

    Klar habe ich mir auch schon selber Makros gebastelt, um Template-Klassen bzw. Methoden eleganter definieren bzw. aufrufen zu können, aber es besteht schon ein Unterschied ob man Makro-Funktionen bewußt einsetzt oder aber durch irgendwelche Headerdateien (evtl. vom SDK bzw. Fremdbibliotheken) komische Defines aufgezwungen bekommt (bzw. diese dann erst selber wieder per '#undef' abschalten muß).

    Aber bis C++ ein vernünftiges Modul-Konzept hat, wird es leider noch lange dauern...


  • Mod

    hustbaer schrieb:

    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. 🙄

    Du kannst alles mit Assembler schreiben. Wozu also eine Hochsprache? Get over it. Ach, wozu Assembler. Einfach direkt in Maschinensprache schreiben. Da das ja voll ausreichend ist, ist es egal wie schlecht das zum Entwickeln ist. Jedenfalls sollte die Menschheit niemals auf irgendeinem Gebiet nach Erleichterungen streben.[quote]

    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.

    Ganz einfacher Vorschlag: defines die den Scope beachten? Metafunktionen die zur Compilezeit ausgewertet werden, so ähnlich wie Templatemetaprogrammierung, nur imperativ anstatt funktional*? Schon haste sehr viele der Probleme des Präprozessors gelöst und automatische Codegenerierung geht trotzdem noch. Aber nein, bloß keine Veränderung, das Leben könnte ja leichter werden 🙄 .

    *: Dies würde die Schwierigkeit natürlich auf den Compilerbau abwälzen. Aber darum geht es ja: Der Präprozessor war extrem einfach zu implementieren und man brauchte ihn um gewisse Probleme zu lösen. Aber die beste Lösung für den Entwickler der Anwendungsprogramme (nicht aus Sicht des Compilerentwicklers) ist er nicht.



  • *: Dies würde die Schwierigkeit natürlich auf den Compilerbau abwälzen. Aber darum geht es ja: Der Präprozessor war extrem einfach zu implementieren und man brauchte ihn um gewisse Probleme zu lösen. Aber die beste Lösung für den Entwickler der Anwendungsprogramme (nicht aus Sicht des Compilerentwicklers) ist er nicht.

    Das ist auch so ein Punkt der mir bei C++ innerhalb der letzten Monate, in denen ich angefangen habe mich mit Haskell zu beschäftigen, aufgefallen ist: Warum muss ich mir als Programmierer Gedanken um die Codegenerierung machen?
    Warum kann der Compiler nicht selber wissen, welche Datei er schon kompiliert hat?
    Warum muss ich darauf achten, dass Funktionen zumindest deklariert sind, bevor ich sie benutze? Der Kompiler könnte doch einfach nach der Definition in den eingebundenen Dateien suchen und erst wenn er sie überhaupt nicht findet einen Fehler ausgeben.
    Warum kann ich Templates nicht genauso einfach in .h und .cpp Dateien aufteilen, wie jede andere Klasse auch?

    Sind das alles Altlasten aus C oder nimmt das C++ Standardisierungskomitee hier Rücksicht auf die Kompilerhersteller, weil das so schwer zu implementieren ist?



  • Freed schrieb:

    Warum kann ich Templates nicht genauso einfach in .h und .cpp Dateien aufteilen, wie jede andere Klasse auch?

    Es hat ja mal einen Versuch mit dem Schlüsselwort export gegeben, das sich aber äusserst schlecht in die Sprache integriert hat und im Endeffekt mehr Nach- als Vorteile gebracht hat. Why We Can't Afford Export

    Freed schrieb:

    Sind das alles Altlasten aus C oder nimmt das C++ Standardisierungskomitee hier Rücksicht auf die Kompilerhersteller, weil das so schwer zu implementieren ist?

    Das ganze Konzept mit Aufteilung in Header- und Implementierungsdatei ist von C geerbt. Doch eine Umsetzung eines Modulsystems wie in anderen Sprachen ist in C++ nicht so einfach, weil die Sprache um einiges komplexer ist. Getrennte Deklarationen und Definitionen, ODR, verschiedene Speicherklassen (Linkage), Templates, Inline-Funktionen sind alles Dinge, die eng mit dem Header-System von C++ zusammenhängen.


  • Mod

    Freed schrieb:

    Sind das alles Altlasten aus C oder nimmt das C++ Standardisierungskomitee hier Rücksicht auf die Kompilerhersteller, weil das so schwer zu implementieren ist?

    Vermutlich beides. Auf diese Weise ist es jedenfalls ausreichend den Code genau einmal durchzugehen. Das macht den Compilerbau einfach und das Compilieren relativ schnell. Und das mit der Aufteilung der Templates nach .h und .cpp steht im Standard sogar drin, aber das kann nur kaum ein Compiler (soweit ich weiß nur einer), weil es ziemlich aufwändig zu implementieren ist. Daher wurde c++0x auch ausgiebiger mit den Compilerherstellern diskutiert als der Standard mit den Templates, so dass man nicht wieder Features zur Sprache hinzufügt, die nur schwer zu implementieren sind.

    edit: Zu langsam. Und im Gegensatz zu Nexus habe ich noch nicht einmal den schönen Link dabei 😞 .



  • SeppJ schrieb:

    Auf diese Weise ist es jedenfalls ausreichend den Code genau einmal durchzugehen. Das macht den Compilerbau einfach und das Compilieren relativ schnell.

    ja, das war zur Zeit der Erfindung von C vor ca. 39 Jahren ein gewichtiges Argument für den single-pass compiler.

    Inzwischen ist 2011, und Computer sind 3 Größenordnungen schneller geworden. Außerdem gibt es makefiles zum inkrementellen compilieren.



  • !rr!rr__ schrieb:

    Inzwischen ist 2011, und Computer sind 3 Größenordnungen schneller geworden. Außerdem gibt es makefiles zum inkrementellen compilieren.

    Und es gibt Sprachen, die die geforderten Annehmlichkeiten bieten. Warum also C oder C++ nutzen, wenn es was besseres gibt? Nur um rum meckern zu können?
    Ich für meinen Teil finde es so gut wie es ist. Ich weiß was passiert und brauch mir nicht um irgendeine Compilermagie Gedanken machen. Weil alles was passieren kann und soll habe ich selber in der Hand. Man kann damit Käse machen, aber wie sagte jemand mal, deswegen studiert man auch und lässt nicht jeden von der Straße basteln. Und ganz ehrlich, es würde mir kein ruhiges Gefühl bescheren, wenn die Steuerungssyteme in einem Flugzeug in irgendeinem Basic-Dialekt, von einem ungelernten auf der Strasse zusammen geklickt werden. Nur weil es ja geht.
    Ein Messer nutzt man auch, obwohl man sich damit schneiden könnte. Brot und Käse kann man ja auch geschnitten kaufen.



  • Und es gibt Sprachen, die die geforderten Annehmlichkeiten bieten. Warum also C oder C++ nutzen, wenn es was besseres gibt? Nur um rum meckern zu können?

    Ich denke das ist eher als konstruktive Kritik gemeint. Man kann auch ruhig mal ein Sprachdefizit nennen ohne damit gleich die ganze Sprache schlecht zu reden. Ein gutes Modulsystem, das ja lediglich die Codeorganisation beeinflussen würde und nicht irgendwelche anderen Spracheigenschaften, würde C++ sicherlich gut tun.



  • Nick Unbekannt schrieb:

    Weil alles was passieren kann und soll habe ich selber in der Hand.

    das ist je nach Anwendung ein Vorteil, aber nicht immer.

    ob z.B. wirklich jeder Programmierer bessere "custom" garbage-collection Routinen programmieren kann, als die teils durch jahrelange Erfahrung optimierten GCs, die manche andere Sprachen vorinstalliert haben?

    Die chronische Flut an patches auf Sicherheitslücken in manchen gängigen OS und mancher Alltags-Software ist auch nicht gerade ein unanfechtbarer Nachweis für die Überlegenheit dieses Ansatzes.

    Nick Unbekannt schrieb:

    Man kann damit Käse machen, aber wie sagte jemand mal, deswegen studiert man auch

    wieso "auch" ? ich dachte, man studiert nur, um Käse zu machen? 😮



  • !rr!rr__ schrieb:

    ob z.B. wirklich jeder Programmierer bessere "custom" garbage-collection Routinen programmieren kann, als die teils durch jahrelange Erfahrung optimierten GCs, die manche andere Sprachen vorinstalliert haben?

    Dafür gibt es in C++ die Standard-Library. Und gerade die GC wird von vielen als großer Nachteil angesehen.

    !rr!rr__ schrieb:

    Die chronische Flut an patches auf Sicherheitslücken in manchen gängigen OS und mancher Alltags-Software ist auch nicht gerade ein unanfechtbarer Nachweis für die Überlegenheit dieses Ansatzes.

    Was hat das mit der Sprache zu tun?

    !rr!rr__ schrieb:

    wieso "auch" ? ich dachte, man studiert nur, um Käse zu machen? 😮

    Das "auch" bezog sich nicht auf das Käse machen. Sondern, dass du studierst um kein Käse zu machen. Weil du nicht nur weißt das es funktionieren könnte, sondern auch warum. Und mit diesem Wissen kannst du auch die Fälle abdecken, wenn es mal nicht funktionieren sollte.

    Und wie schon angemerkt, wenn man andere Sprachen für geeigneter hält, was hindert einen daran diese zu nutzen? Ich würde auch nicht auf die Idee kommen alles in C++ umsetzen zu wollen. Das dauert einfach manchmal viel zu lange, zu kompliziert, zu Fehleranfällig. Dafür gibt es aber auch wieder andere Fälle, wo mir C++ ideal für erscheint. Und außer für die include Guards sehe ich auch keinen Grund, warum man unbedingt mit dem Präprozessor arbeiten müsste. Und dabei halten sich die Fehlerquellen in Grenzen. Sie unterschieden sich noch nicht mal von den Fehlerquellen, die in C/C++ möglich sind.



  • Ich würde mir ein Modulsystem wünschen. #include ist einfach nervig und das ganze geht auf Kosten der Compilezeit. Ansonsten finde ich den Präpro ein nützliches Werkzeug. Man muss sich halt über die Folgen bewusst sein. Aber gerade bei den Namespace-Gewurschteleien in C++-Headern kann der Präpro den Code leserlich machen

    zB

    #define IsSequence(X) boost::fusion::traits::is_sequence< X >::value
    
        template<typename BigfloatL, typename BigfloatR,
                 bool Lcompiletime = IsSequence(BigfloatL),
                 bool Rcompiletime = IsSequence(BigfloatR)>
        struct linear_expansion_sum;
    
    #undef IsSequence
    

    ist zB deutlich leserlicher als

    template<typename BigfloatL, typename BigfloatR,
                 bool Lcompiletime = boost::fusion::traits::is_sequence< BigfloatL >::value,
                 bool Rcompiletime = boost::fusion::traits::is_sequence< BigfloatL >::value>
        struct linear_expansion_sum;
    

    und vermeidet dank #undef die Nebenwirkungen.

    In einigen Fällen ist sogar Boost.PP ganz nützlich.


Anmelden zum Antworten