Ungewöhnliches Verhalten der C-Preprocessors



  • Dies ist zwar richtig, ändert aber an der Tatsache doch auch nichts. Wenn z.B. von meinem Code BYTE_ORDER nicht definiert ist, kommt trotzdem LITTLE als warnung. Ich würde aber eigentlich erwarten, dass er mir sagt, dass er die Makros nicht kennt und daher keine Ersetzungen durchführen kann. Deshalb bezog sich die Frage darauf, ob das das Standardverhalten ist. Wie man da drumrumprogrammiert, ist mir schon klar und habe ich auch schon längst in meinem Projekt umgesetzt, aber ich wollte halt mal generell wissen, wie es sich dort mit dem Standard verhält.

    Das Problem entstand übrigens, als ich Quelltext von Linux zu Windows portierte. In der entsprechenden Datei war halt der von mir gezeigte (stark vereinfacht) Code drin und es wurde dieses included <arpa/inet.h> und unter Linux funktionierte es auch einwandfrei, da jeweils alle Makros definiert waren. Für Windows habe ich <winsock2.h> eingebunden. Dann musste ich beim Testen feststellen, dass es nicht so funktionierte, wie es angedacht war, also ging ich auf die Suche. Da fand ich dann heraus, dass der CPP meiner Meinung falsch arbeitet, wenn ein Makro nicht definiert ist. Daher die Frage nach dem Standard.



  • mysterio schrieb:

    Ich würde aber eigentlich erwarten, dass er mir sagt, dass er die Makros nicht kennt und daher keine Ersetzungen durchführen kann.

    Es ist dumme Textersetzung.

    Wenn BYTE_ORDER nicht definiert ist, dann laesst er dort BYTE_ORDER stehen. Woher soll er wissen dass du BYTE_ORDER ersetzen willst?

    Bei einem #if koennte er ja uU raten, aber dann waere das verhalten ja inkonsistent wenn er nur innerhalb eines #ifs warnen wuerde und ausserhalb nicht.

    Praeprozessor ist einfache, dumme, textersetzung - keine script sprache.



  • Ok gut, verstehe ich. Die Frage warum er dennoch den ersten Zweig des #if ausführt ist für mich immer noch unklar, denn BYTE_ORDER, LITTLE_ENDIAN und auch BIG_ENDIAN sind nicht definiert, folglich bleibt es so im Text stehen.

    Shade Of Mine schrieb:

    Bei einem #if koennte er ja uU raten, aber dann waere das verhalten ja inkonsistent wenn er nur innerhalb eines #ifs warnen wuerde und ausserhalb nicht.

    Aber genau dies tut er ja, er rät. Warum zum Teufel etwa wird denn die #if Klausel gewählt und nicht etwa die #elif oder das #else.

    Tut mir leid, aber ich versteh es immer noch nicht. Ist dieses Verhalten bei anderen CPP anderer Compiler außer gcc auch so??? Oder ist dies ein Bug im CPP des gcc oder ...



  • mysterio schrieb:

    Shade Of Mine schrieb:

    Bei einem #if koennte er ja uU raten, aber dann waere das verhalten ja inkonsistent wenn er nur innerhalb eines #ifs warnen wuerde und ausserhalb nicht.

    Aber genau dies tut er ja, er rät. Warum zum Teufel etwa wird denn die #if Klausel gewählt und nicht etwa die #elif oder das #else.

    Er raet nicht. Ich weiss nicht wonach er hier genau vergleicht, aber dein Code ist einfach falsch. Hier muss mit ifdef bzw if defined gearbeitet werden.

    ich persoenlich tippe, dass 2 undefinierte werte bei einem == vergleich true ergeben. aber der punkt ist einfach, dass der code fehlerhaft ist. man verwendet keine undefinierten werte.



  • Shade Of Mine schrieb:

    ich persoenlich tippe, dass 2 undefinierte werte bei einem == vergleich true ergeben.

    Ist ja jz auch nicht so unlogisch!? Macht der MSVC btw auch so ^^

    #define A 12
    
    #if A == B
    int r = 2;
    #else
    int r = 1;
    #endif
    

    mit der ersten zeile ist r logischerweiße 1 - ohne ist r == 2
    so hätte ich es auch erwartet - und wenn gcc und msvc mal das gleiche machen, wird es wohl auch iwo im standard so stehen oder zumindest wird es kein bug sein ^^

    bb



  • Shade Of Mine schrieb:

    Er raet nicht. Ich weiss nicht wonach er hier genau vergleicht, aber dein Code ist einfach falsch. Hier muss mit ifdef bzw if defined gearbeitet werden.

    Ja, aber genau dies wollte ich wissen, welches Verhalten der Standard ist, wenn etwas nicht definiert ist.

    Und der Code an sich ist nicht wirklich falsch, oder die Leute von der GNU C-Lib machen es auch falsch (siehe /usr/include/ctype.h). Ich weiss, dass dies keine Begruendung ist, aber ich finde es schon schlecht, wenn hier der CPP etwas macht, was man halt nicht erwartet. Wenn dies so Standard ist, ok, aber diese Frage konnte mir leider noch keiner beantworten. Es geht also nicht um den Code von mir, sondern um die Klärung der Frage.



  • mysterio schrieb:

    Ja, aber genau dies wollte ich wissen, welches Verhalten der Standard ist, wenn etwas nicht definiert ist.

    Wär' mir neu, dass es für den Präprozessor einen Standard gibt.

    cheers, Swordfish



  • Das Verhalten des Präprozessors ist standardisiert.. oder verstehe ich dich da falsch?!



  • Ich nehm alles zurück *duck*

    cheers, Swordfish


  • Administrator

    Ich habe gerade mal probiert den Standard in dem Punkt zu lesen. Wenn ich es richtig verstanden habe, dann wird alles, was nach dem rekursiven Ersetzen der Preprozessor Anweisungen übrig bleibt, durch eine 0 ersetzt.

    Also wenn wir deinen Code nehmen:

    #if (BYTE_ORDER==LITTLE_ENDIAN) 
    #warning LITTLE 
    #elif (BYTE_ORDER==BIG_ENDIAN) 
    #warning  BIG 
    #else 
    #warning BYTE_ORDER not defined 
    #endif
    

    Und wenn nun BYTE_ORDER , LITTLE_ENDIAN , BIG_ENDIAN nicht bekannt sind, dann ergibt sich:

    #if (0==0) 
    #warning LITTLE 
    #elif (0==0) 
    #warning  BIG 
    #else 
    #warning BYTE_ORDER not defined 
    #endif
    

    Dadurch wird natürlich immer eine Warnung "LITTLE" ausgegeben. Wobei allerdings noch gesagt werden muss, dass #warning laut Standard 98 gar nicht existiert.

    Wenn BYTE_ORDER gesetzt ist und zwar wie nachfolgend, der Rest aber nicht.

    #define BYTE_ORDER
    

    Dann wird aus deinem Code das folgende:

    #if (==0) 
    #warning LITTLE 
    #elif (==0) 
    #warning  BIG 
    #else 
    #warning BYTE_ORDER not defined 
    #endif
    

    Was dann einen Fehler werfen wird, da die Ausdrücke nicht wohlgeformt sind.

    Im Standard 1998 Kapitel 16.1
    Vor allem der Abschnitt 4 dürfte die Sache erklären.

    Grüssli



  • @Dravere danke, genau dies wollte ich wissen.

    Dies klärt jetzt das Verhalten, aber merkwürdig finde ich es schon (trotz Standard), dass der CPP die Sachen dann einfach durch 0 ersetzt. Ebenso könnte er ja dann auch eine Warnung ausgeben, dass er keine Ersetzung vornehmen konnte, da er etwas nicht kennt.

    Danke nochmal allen


  • Administrator

    mysterio schrieb:

    ..., aber merkwürdig finde ich es schon (trotz Standard), dass der CPP die Sachen dann einfach durch 0 ersetzt.

    Nein, das hat durchaus einen Sinn, wenn ich das richtig verstanden habe. (Der Standard ist ein wenig komplex formuliert in diesem Punkt, wie ich finde)

    Nehmen wir diesen einfachen Code:

    #if USE_THIS_THAT
    // Anweisungen
    #endif
    

    Wenn USE_THIS_THAT nicht definiert oder auf 0 definiert ist, werden die Anweisungen im Rumpf nicht eingefügt.

    // Nicht definiert oder:
    #define USE_THIS_THAT 0
    

    Nur wenn USE_THIS_THAT auf 1 definiert ist, werden die Anweisungen eingefügt.

    #define USE_THIS_THAT 1
    

    Die Sache ist nämlich, dass der Präprozessor in den Bedingungen mit konstanten Ganzzahlen arbeitet, wobei wie in C++ selbst alles ungleich 0 wahr ist und 0 falsch. defined liefert zum Beispiel immer entweder eine 1 oder eine 0.

    Grüssli



  • Ich habe mir mal im Draft des Standards den Punkt durchgelesen und stimme Dir absolut zu. Ich habe dann auch mal beim cpp des gcc geschaut, ob es da was gibt, damit er mich informiert, wenn er solche Ersetzungen macht und siehe da -Wundef ist mein Freund. Leider ist dieser Switch bei -Wall oder -pedantic nicht standardmäßig mit dabei. Daher hatte er mich nie gewarnt.

    Nochmals danke und noch einen schönen Abend



  • Also für sinnvoll halte ich es nicht.
    Für den "#if UNDEFINED_MACRO" Fall gibts ja schliesslich #ifdef, braucht man nicht auch noch "#if" so umwursteln dass es dasselbe kann.

    ---

    Das ganze ist historisch so gewachsen, und wurde zu einem Zeitpunkt standardisiert, wo es mächtig unangenehm gewesen wäre bestimmte Dinge noch zu ändern.
    Die Frage nach dem Sinn hinter irgendwas ist daher auch oft nicht befriedigens zu beantworten.


  • Administrator

    hustbaer schrieb:

    Also für sinnvoll halte ich es nicht.
    Für den "#if UNDEFINED_MACRO" Fall gibts ja schliesslich #ifdef, braucht man nicht auch noch "#if" so umwursteln dass es dasselbe kann.

    Du solltest das Beispiel nochmals anschauen, es ist eben kein #ifdef . Wenn ich die gleiche Funktionalität erreichen möchte, ohne diese spezielle Klausel, dann würde mein Code so aussehen:

    #ifdef USE_THIS_THAT
    #  if USE_THIS_THAT
    // Anweisungen
    #  endif
    #endif
    

    Leicht unnötig kompliziert? Und wenn ich mehrere Makros habe, welche auf ihr Vorhandensein geprüft werden müssen, dann wäre das sogar extrem komplexer.

    Das praktische an meinem Code oben ist ja, dass man etwas auch explizit abschalten kann und nicht nur abschalten dadurch, dass man nichts angibt.

    #define USE_THIS_THAT 0 // Explizit aus
    

    Sofern man den Präprozessor richtig benutzt, also nicht so wie es mysterio gemacht hat, gibt es da auch gar kein Problem.

    Grüssli



  • Dravere schrieb:

    Sofern man den Präprozessor richtig benutzt, also nicht so wie es mysterio gemacht hat, gibt es da auch gar kein Problem.

    So kann ich dies ja nun nicht wirklich stehen lassen. Zunächst, habe ich fremden Code portiert und dabei dieses Verhalten festgestellt. Und es ist ja nun auch so, dass es absolut legaler CPP-Code ist. Wird, wie ich ja schon in vorangegangen Postings schrieb, ebenfalls so in Standarddateien(z.B. ctype.h) der libc so gemacht. Und es ist auch üblich dies so zu machen, habe ich schon vielfach auch in anderen Projekten gesehen. Es wäre halt meiner Meinung nach sinnvoll, das z.B. -Wundef bei -Wall -pedantic mit dabei ist. Dann wäre es aufgefallen, dass ein Makro nicht definiert ist und ich hätte nicht ewig suchen müssen, warum das Programm so ein merkwürdiges (da falsch konfiguriert) Verhalten zeigt. Wenn man keine Warnung dieser Art haben, kann man auch einzelne Warnungen explizit unterdrücken.

    Da der Standard hier auch eindeutig ist, wie ich jetzt ja weiss, finde ich halt, dass vom Compiler gewarnt werden sollte, um den Programmierer zu unterstützen. Schliesslich warnt er ja auch bei ungenutzten Variablen und vielen anderen Sachen. Und ja, jetzt brauch keiner schreiben, dass ungenutzte Variablen vom Compiler erkannt werden und dass andere der Preprocessor macht. Dies ist mir klar und sollte auch nur ein Vergleich sein.

    Grüße und einen schönen Sonntag noch


  • Administrator

    mysterio schrieb:

    ..., dass es absolut legaler CPP-Code ist.

    Nur weil etwas legal ist, heisst es nicht, dass es auch korrekt ist. Gerade in C und C++ können Dinge oft in der Syntax legal sein, tatsächlich aber völlig falsch und zum Teil undefiniert.

    mysterio schrieb:

    Wird, wie ich ja schon in vorangegangen Postings schrieb, ebenfalls so in Standarddateien(z.B. ctype.h) der libc so gemacht. Und es ist auch üblich dies so zu machen, habe ich schon vielfach auch in anderen Projekten gesehen.

    Daran ist auch nichts auszusetzen, wenn man denn das entsprechende Verhalten möchte, welches daraus entsteht. Vielleicht ist es in den Fällen überall so, dass mit Sicherheit feststeht, dass die Makros ganz sicher definiert sind, bevor sie verwendet werden. Es kommt daher auf den Zusammenhang an, ob etwas richtig oder falsch ist. In deinem Kontext war es aber nunmal falsch. Du hast den Präprozessor falsch angewendet.

    mysterio schrieb:

    Es wäre halt meiner Meinung nach sinnvoll, das z.B. -Wundef bei -Wall -pedantic mit dabei ist. Dann wäre es aufgefallen, dass ein Makro nicht definiert ist und ich hätte nicht ewig suchen müssen, warum das Programm so ein merkwürdiges (da falsch konfiguriert) Verhalten zeigt. Wenn man keine Warnung dieser Art haben, kann man auch einzelne Warnungen explizit unterdrücken.

    Es muss ja auch gar nicht gewarnt werden. Es kommt mir ein wenig vor, wie die Leute, welche eine Warnung für folgenden Code verlangen:

    void foo(int i, int a)
    {
      if(i = a) // <- hierfür, da womöglich == gemeint war.
      {
        // ...
      }
    }
    

    Dabei ist das legaler C++ Code, kann aber im Kontext den man wollte falsch sein. Es kann aber auch gewollt sein. Deshalb wird es hierfür auch keine Warnung geben. Wieso sollte man Warnungen für etwas bekommen, was man wollte und legal korrekter C++ Code ist?

    Grüssli



  • Dravere schrieb:

    Wieso sollte man Warnungen für etwas bekommen, was man wollte und legal korrekter C++ Code ist?

    mir haben ähnliche warnungen schon geholfen.
    es würde nur dazu führen, daß man sich im seltenen fall so einer zuweisung ein wanungsvermeidungsidiom einfallen lassen muss, zum beispiel einfach

    if( (i = a) != 0 )
    

    oder

    if( i = a , i != 0 )
    

    oder

    if( i = a , i )
    

    oder

    i = a;
    if( i != 0 )
    

    oder

    i = a;
    if( i )
    


  • @Dravere ich glaube Du liegst mit deiner Aussage, dass dies keine Warnung erzeugt nur zur Hälfte richtig, denn

    Wenn ich den Compiler ohne weitere Optionen aufrufe außer den zu übersetzenden Dateien, stimmt sie, aber wenn ich -Wall benutze kommt sehr wohl eine Warnung. Und genau dieses meinte ich ja, als ich schrieb dass bei undefinierten Makros auch gewarnt werden könnte/sollte, wenn man -Wall benutzt. Schließlich will man ja alle Warnungen haben. Die Warnung bei deinem Code könnte man wie volkard schrieb ja auch recht einfach umgehen bzw. einen entsprechenden Compilerswitch benutzen, der diese dann abschaltet.

    Folglich nochmals ich meine nicht, dass der Compiler einfach so warnen sollte, aber wenn ich schon -Wall und -pedantic aus Sicherheitsgründen benutze, um alle möglichen Fallstricke zu entdecken, sollte er auch vor undefinierten Makros warnen. So sehe ich dies zumindest. Und nur weil etwas momentan bei den Compilern oder im Standard definiert ist, heißt dies ja nicht automatisch, dass dies immer die beste Herangehensweise oder das Optimum ist. Ich will jetzt aber keine Dikussion darüber lostreten, weshalb ich die letzte Aussage nicht falsch verstanden wissen möchte.

    Ein weiteres Beispiel wäre ja auch ein Tippfehler beim Schreiben einer Makroabfrage im #if und schon steht dort einen 0. Wenn der CPP aber warnen würde, dass das Makro nicht definiert ist, wäre dies nicht so tragisch, da leicht zu entdecken. Und lieber eine Warnung mehr und Hilfe vom Compiler, als still schweigend etwas zu machen, was zwar Standard ist, aber z.B. beim Schreibfehler eben nicht die Intention war.

    Grüße


Anmelden zum Antworten