Makro verhält sich fehlerhaft bei GNU Compiler Linux u. Windows
-
Hi!
Ich habe das Problem, dass sich ein Makro mit einigen Operatoren fehlerhaft unter gcc Linux/Windows verhält, während der Code unter Borland oder Sun Compiler richtig arbeitet und die korrekten Werte herauskommen. Ich weiß nicht, woran es liegt, dass bei GCC der Code zweimal ausgeführt wird. Ich habe auch ohne Optimierung -O kompiliert: der gleiche Effekt. U32 ist Datentyp ULONG32.
lpnadc->pu_buf= ((U32)((*lpnadc->R3P++))<<24) | \ ((U32)((*lpnadc->R3P++))<<16) | \ ((U32)((*lpnadc->R3P++))<< 8) | \ ((U32)((*lpnadc->R3P++)) ); \
Das ist der Ausschnitt im Makro, der nicht unter GCC geht aber mit Borland und Sun. Behandelt GCC Makros anders? Vielen Dank für Tipps und Anregungen.
Gruß
jesse
-
Ich seh da kein Makro. Es fällt aber auf, dass
lpnadc->R3P++
mehrfach zwischen Sequenzpunkten verändert wird. Das hat undefiniertes Verhalten.
-
Das ganze Makro ist hier
#define PULL(a,b) \ { \ a <<= b; \ lpnadc->R9D -= b; \ if (lpnadc->R9D < 0) \ { \ a |= (lpnadc->pu_buf << (-lpnadc->R9D)) & MASK(b); \ lpnadc->pu_buf= ((U32)((*lpnadc->R3P++))<<24) | \ ((U32)((*lpnadc->R3P++))<<16) | \ ((U32)((*lpnadc->R3P++))<< 8) | \ ((U32)((*lpnadc->R3P++)) ); \ lpnadc->R9D += 32; \ a |= (lpnadc->pu_buf >> lpnadc->R9D); \ } \ else a |= (lpnadc->pu_buf >> lpnadc->R9D) & MASK(b); \ }
-
Auf die Gefahr hin, mich zu wiederholen: Das Problem ist, dass ein und dasselbe Objekt mehrfach zwischen zwei Sequenzpunkten modifiziert wird. Dass das in einem Makro passiert ist vollkommen unerheblich. Der Code ist falsch. Er sollte vermutlich etwa so aussehen (falls erwartet wurde, dass die Seiteneffekte der ++-Ausdrücke von links nach rechts ausgeführt werden):
lpnadc->pu_buf = (U32)(lpnadc->R3P[0] << 24) \ | (U32)(lpnadc->R3P[1] << 16) \ | (U32)(lpnadc->R3P[2] << 8) \ | (U32)(lpnadc->R3P[3] ); \ lpnadc->R3P += 4;
-
Wieso als Makro?
-
Ethon schrieb:
Wieso als Makro?
Da ich schon weiß, welche Antwort kommen wird:
Der GCC kann so neuartige Standards wie C99. Da gibt es inline-Funktionen.
-
Bashar schrieb:
Auf die Gefahr hin, mich zu wiederholen: Das Problem ist, dass ein und dasselbe Objekt mehrfach zwischen zwei Sequenzpunkten modifiziert wird. Dass das in einem Makro passiert ist vollkommen unerheblich. Der Code ist falsch. Er sollte vermutlich etwa so aussehen (falls erwartet wurde, dass die Seiteneffekte der ++-Ausdrücke von links nach rechts ausgeführt werden):
lpnadc->pu_buf = (U32)(lpnadc->R3P[0] << 24) \ | (U32)(lpnadc->R3P[1] << 16) \ | (U32)(lpnadc->R3P[2] << 8) \ | (U32)(lpnadc->R3P[3] ); \ lpnadc->R3P += 4;
Fast. Der Cast-Operator wirkt im Original auf den dereferenzierten Wert, nicht erst auf das shift-Ergebnis.
-
Vielen Dank für die tollen Hinweise. Aber muss ein guter Compiler nicht meckern, wenn der Code falsch ist?
-
Kompilierst du mit -Wall und -Wextra?
-
Testhalber hatte ich erst mit -Wall kompiliert. Es gab keine Warnings. Dann habe ich ohne -Wall kompiliert.
-
Sorry. Ich habe mich geirrt. Ihr habt ja so recht. Das Objekt DYN.c, das diesen Code enthält und ich als DYN.o erzeuge und zu meinem Testprogramm tst.c binde, hatte ich ohne -Wall übersetzt, nur tst.c mit -Wall gebunden und übersetzt. Ich habe jetzt DYN.c auch mit -Wall übersetzt und bekomme eine Warning gemeldet: "Operation auf lpnadc->R3P könnte undefiniert sein". Der GCC Compiler hat doch recht. Warum ein Borland-Compiler vom Kollegen das ignoriert, verstehe ich nicht. Borland kann ja auch nicht C99-Standard (Inline-Funktionen).
-
Jetzt ist ein Streit zwischen mir und meinem Kollegen entbrannt, ob der GCC einen Bug im Parallizer hat und das Original Statement hätte von links nach rechts abarbeiten müssen. Sein Borland Compiler bringt nicht einmal eine Warning bei dem Statement.
-
kjesse schrieb:
Jetzt ist ein Streit zwischen mir und meinem Kollegen entbrannt, ob der GCC einen Bug im Parallizer hat und das Original Statement hätte von links nach rechts abarbeiten müssen. Sein Borland Compiler bringt nicht einmal eine Warning bei dem Statement.
Welcher Parallizer?! Hier wird nichts parallelisiert.
Das Verhalten ist nach ISO/IEC 9899 undefiniert. Undefiniert heißt, der Compiler kann machen, was er will. Er kann das Programm übersetzen, oder nicht. Er kann eine Warnung ausgeben, oder nicht. Wenn er das Programm übersetzt, kann die Ausführung dieses Code dazu führen, dass die Erde explodiert. Das wäre laut Standard kein verbotenes verhalten.
Dein Kollege hat keine Ahnung.
EDIT:
Schon die Annahme, ein Compiler müsste irgendetwas von links nach rechts abarbeiten, ist falsch. Er muss lediglich sämtliche Sequenzpunkte von links nach rechts abarbeiten. Was zwischen zwei Sequenzpunkten geschieht, ist dem Compiler überlassen. Und in diesem Makro befinden sich keine Sequenzpunkte. Das sollte man als C-Programmierer aber wissen
-
LordJaxom schrieb:
EDIT:
Schon die Annahme, ein Compiler müsste irgendetwas von links nach rechts abarbeiten, ist falsch. Er muss lediglich sämtliche Sequenzpunkte von links nach rechts abarbeiten. Was zwischen zwei Sequenzpunkten geschieht, ist dem Compiler überlassen. Und in diesem Makro befinden sich keine Sequenzpunkte. Das sollte man als C-Programmierer aber wissenIch glaube der Kollege hat | mit || verwechselt.
-
Danke für die Unterstützung. Ich habe das genauso gesehen. Es ist der Code vom Kollegen, der auf Borland läuft und bei mir unter GCC eine Warning wirft. Der Kollege meinte, dass man den GCC in die Tonne treten sollte, wenn er nicht parallelisiert. Der Borland kann auch keine Inline-Funktionen. Der neue ANSI-konforme Code läuft übrigens perfekt.
-
kjesse schrieb:
Der Kollege meinte, dass man den GCC in die Tonne treten sollte, wenn er nicht parallelisiert.
Vielleicht sollte man Deinen Kollegen in die..... lassen wir das.
Wie kommt er darauf dass ein Compiler, ohne irgendwelche Erweiterungen, ohne dazu durch den Programmierer angewiesen worden zu sein, und ohne irgendwelche Threadfunktionen manuell benutzt zu haben, automatisch parallelisieren sollte?
Davon abgesehen, wieso sollte ausgerechnet eine parallele Ausführung sicherstellen, dass die vier Inkrementierungen in der richtigen Reihenfolge abgearbeitet werden? Der Witz an Parallelisierung wäre doch gerade, dass mehrere gleichzeitig abgearbeitet würden.
-
LordJaxom schrieb:
Wie kommt er darauf dass ein Compiler, ohne irgendwelche Erweiterungen, ohne dazu durch den Programmierer angewiesen worden zu sein, und ohne irgendwelche Threadfunktionen manuell benutzt zu haben, automatisch parallelisieren sollte?
Er meint wohl Instruction-Scheduling derart, dass parallele Pipelines bestmöglich ausgelastet werden. Nichts mit Threads.
Und er ist der Meinung, dass bei dieser Umordnung die Abhängigkeit der Inkrementierungen nicht richtig berücksichtigt wird. Da hätte er recht, wenn in C++ strenge Links-Nach-Rechts-Auswertung vorgeschrieben wäre, und wenn mehrfache Modifikationen zwischen Sequenzpunkten nicht explizit undefiniertes Verhalten hätten :p
-
Der Kollege hatte dann folgenden Code noch ausprobiert, der den gleichen Fehler beim Programmablauf bringt, aber keinerlei Warning mit GCC:
lpnadc->pu_buf = *lpnadc->R3P; lpnadc->R3P++; lpnadc->pu_buf=lpnadc->pu_buf<<8; \ lpnadc->pu_buf|= *lpnadc->R3P; lpnadc->R3P++; lpnadc->pu_buf=lpnadc->pu_buf<<8; \ lpnadc->pu_buf|= *lpnadc->R3P; lpnadc->R3P++; lpnadc->pu_buf=lpnadc->pu_buf<<8; \ lpnadc->pu_buf|= *lpnadc->R3P; lpnadc->R3P++; \
Hier wird zwar sequentiell gearbeitet, aber man hat das gleiche Problem mit dem Inkrementieren. Es kommt keine Warning, aber der gleiche Fehler beim Ausführen.
-
Ethon schrieb:
Wieso als Makro?Da ich schon weiß, welche Antwort kommen wird:
Der GCC kann so neuartige Standards wie C99. Da gibt es inline-Funktionen.Wie heißt es so schön in Effective C++: schon nur an Makro's zu denken ist schmerzhaft.
Und was spricht gegen eine Micki Maus Funktion ?
Makro's haben hübsche Seiteneffekte, da es bei Makro um ein dummen Ersetzungsvorgang handelt, und sind ebenfalls auch noch schwer zu debuggen. Sie sind daher nicht empfehlenswert.
#define min(a, b) (a < b) ? a : b //... c = min(++a, b); // ist äquivalent zu c = (++a < b) : ++a : b;
-
Ich benutze in meinen Programmen Inline-Funktionen, die auch noch schneller als Makros sind. Ich arbeite nur mit GCC auf Windows und Linux. Aber der Borland Compiler kann angeblich keine Inline-Funktionen im ANSI Standard.