Hallo Gemeinde
Ich verstehe nicht, warum gcc bei folgendem Code motzt:
1
1263:uint32tCnt,tDir,tIdx;
2
3
1287:for(tCnt=0;tCnt<NB_TR_CNTS;tCnt++)
4
1288:{
5
1289:for(tDir=0;tDir<NB_DIRS;tDir++)
6
1290:{
7
1291:tIdx=8*tCnt+4*tDir;
8
1292:G_OpData.TravelCnt[tCnt][tDir]=tBuf[tIdx++]
9
1293:+(tBuf[tIdx++]<<8)
10
1294:+(tBuf[tIdx++]<<16)
11
1295:+(tBuf[tIdx++]<<24);
12
1296:}
13
1297:}
file.c:1295: warning: operation on `tIdx' may be undefined
file.c:1295: warning: operation on `tIdx' may be undefined
file.c:1295: warning: operation on `tIdx' may be undefined
Es hat wohl mit dem Post-Inkrement zu tun. Trotzdem finde ich, dass das
so in Ordnung ist.
In Java wäre das wohl ok. In C isses undefiniert, da niemand weiß, wann
der um 1 erhöhte Wert wieder an die Variable zurückgeht.
Ein Übersetzer könnte etwa folgendes draus machen:
1
G_OpData.TravelCnt[tCnt][tDir]=tBuf[tIdx]
2
+(tBuf[tIdx]<<8)
3
+(tBuf[tIdx]<<16)
4
+(tBuf[tIdx]<<24);
5
tIdx+=4;
Es fehlt ein Sequenzpunkt dazwischen. Ansonsten halte ich auch wenig von
solchen Spielereien.
Lösung:
1
G_OpData.TravelCnt[tCnt][tDir]=tBuf[tIdx+0]
2
+(tBuf[tIdx+1]<<8)
3
+(tBuf[tIdx+2]<<16)
4
+(tBuf[tIdx+3]<<24);
5
tIdx+=4;
Übersichtlich und jeder verstehts, ohnes Gehirn zu verknoten.
> Es hat wohl mit dem Post-Inkrement zu tun. Trotzdem finde ich, dass das> so in Ordnung ist.
Ob du das findest, ist aber dem Compiler egal. ;-)
Wie Sven schreibt, fehlt ein Sequenzpunkt zwischen den Inkrements. Das
Verhalten von Code, der eine Variable mehrmals ändert oder ändert und
liest, ohne daß dazwischen ein Sequenzpunkt liegt, ist in C undefiniert.
Daher zurecht die Warnung.
A. K. schrieb:
> Sven P. schrieb:>>> In Java wäre das wohl ok.>> Wie ist das denn in Java definiert?
Keine Ahnung, ich erinnere mich nur an so ein hirnverbranntes Beispiel
für Java, und der Compiler hat keine Warnung ausgespuckt.
Hier isses:
http://www.thestudentroom.co.uk/showthread.php?t=639421
In C/++ wäre das hochgradig undefiniert und totaler Schwachsinn isses
sowieso.
Hi, ich habe letzte Nacht kaum geschlafen, die Heizung war kaputt, hab
seit 7 Uhr Handwerker im Haus, die die Eingangstür wechseln - und Kaffe
ist das einzige was mich jetzt noch am leben hält ;-) Vielleicht liegt
es ja daran, dass ich total auf den Schlauch stehe und das Problem nicht
nachvollziehen kann.
Mein gcc (version 3.4.5 (mingw-vista special)), aufgerufen mit -Wall,
meckert auf jedenfall nicht. Welche gcc version für welches Target mit
welchen compiler flags benutzt Du denn? Und wieso ist das Verhalten
undefiniert? Gibt es deswegen nicht auch die C-Prioritäten? IMHO steht
da doch: Greife auf das Array tBuff mit dem Index tIdx zu, danach
inkrementiere tIdx, danach greife auf das Array tBuff mit dem neuen Wert
von tIdx zu, danach inkrementiere tIdx, danach addiere die Inhalte der
ersten beiden Array zugriffe usw.
Grüße Daniel
Entscheidend ist nicht, ob der Compiler meckert, sondern was die
Sprachdefinition hergibt. Und die ist eindeutig: das Verhalten ist
undefiniert. Weder Prioritäten noch der sogenannte gesunde
Menschenverstand ändern daran etwas.
Daniel H. schrieb:
> IMHO steht> da doch: Greife auf das Array tBuff mit dem Index tIdx zu, danach> inkrementiere tIdx, danach greife auf das Array tBuff mit dem neuen Wert> von tIdx zu, danach inkrementiere tIdx, danach addiere die Inhalte der> ersten beiden Array zugriffe usw.
Ne, es steht nirgendwo, WANN tIdx seinen neuen Wert bekommt. Dafür gibts
ja Sequenzpunkte, und die fehlen hier.
Daniel H. schrieb:
> IMHO steht> da doch: Greife auf das Array tBuff mit dem Index tIdx zu, danach> inkrementiere tIdx,
Nein.
Das steht da eben nicht.
Der Compiler ist in seiner Entscheidung frei, wann genau er das
Inkrementieren durchführen will. Er muss es nur irgendwann machen, ehe
das nächste Statement beginnt (Exakt gesagt: Vor dem nächsten Sequence
Point, aber in dem Fall ist das der ';', also der Zeitpunkt an dem das
nächste Statement beginnt)
Wenn der Compiler will, kann er alle 4 Inkrements bis ans Ende der
Ausführungskette verschieben.
@Daniel: Deine postulierte Abfolge setzt voraus, dass von links nach
rechts ausgewertet wird, und dass in einem Ausdruck enthaltene
Zuweisungen in einer bestimmten Reihenfolge entsprechend der Prioritäten
ausgeführt werden. Dem ist nicht so. Zuweisungen in einem Ausdruck (a++
und ++a sind Zuweisungen) sind erst am schon sattsam erwähnten sequence
point mit Sicherheit durchgeführt, davor kann es sein muss aber nicht
sein. So ist C nun einmal definiert. Beschwerden bitte an Dennis Ritchie
richten.
A. K. schrieb:
> @Daniel: Deine postulierte Abfolge setzt voraus, dass von links nach> rechts ausgewertet wird,
Darauf möchte ich noch näher eingehen, weil es wichtig ist.
Oft wird nämlich Operatoren-Reihenfolge mit Auswerte-Reihenfolge
verwechselt.
Operatoren-Reihenfolge ist definiert.
Es ist zb exakt festgelegt, dass
a + b + c
als
( a + b ) + c
berechnet wird.
Es ist aber nicht festgelegt, in welcher Reihenfolge die Teilausdrücke
a, b, und c ausgewertet (dh. ein Zahlenwert dafür bestimmt) werden.
Der Compiler kann zuerst c auswerten, dann b und dann a (oder jede
andere beliebige Reihenfolge) und dann die Teilwerte gemäss
( a + b ) + c
zum Gesamtwert zusammenführen.
Das ist mit Auswerte-Reihenfolge gemeint: Die Reihenfolge, in der
Teilausdrücke bewertet werden.
Wenn die Teilausdrücke Nebeneffekte besitzen (wie zb hier das Inkrement)
ist alleine dadurch schon ein undefiniertes Verhalten gegeben.
Ein anderes Beispiel:
result = foo() + bar();
Es ist durch die undefinierte Auswertereihenfolge nicht definiert, ob
zuerst die Funktion foo() und erst dann die Funktion bar() aufgerufen
wird, oder umgekehrt. Wenn daher bar() darauf angewiesen ist, dass foo()
zuerst aufgerufen wird (weil es zb einen Wert für bar() einstellen
muss), dann hat man hier ein Problem.
Danke an alle! Jetzt habe auch ich es geschnallt :-)
Bei a = b + b++; kann das Inkrement z.B vor oder nach der Addition
erfolgen, was natürlich zu unterschiedlichen Verhalten führt. Sicher ist
nur, dass b nach dieser Zeile inkrementiert wurde.
Grüße, Daniel
A. K. schrieb:
> Sven P. schrieb:>> In Java wäre das wohl ok.> Wie ist das denn in Java definiert?
Java arbeitet Ausdrücke strikt von links nach rechts ab.
Stephan
Sven P. schrieb:
> Das wiederum ist dann nicht mit der Kurzschluss-Bewertung zu> verwechseln, bei welcher die Auswertereihenfolge festgelegt ist:
Ganz genau.
Geregelt ist das dadurch, dass sowohl && als auch || als Sequence Point
gelten.
Die wichtigsten Sequence Points sind
* && || und Komma Operator (nit zu verwechseln mit dem normalen Komma)
* beim ? in einem ?: Ausdruck
* Am Ende eines vollständigen Ausdrucks (vulgo: beim ';')
Dazu zählt aber auch: Der Ausdruck, der ein if, while etc. steuert
* Beim Aufruf einer Funktion, nachdem die Funktionsargumente
ausgewertet wurden. Aber Achtung: Die Reihenfolge der Auswertung der
Funktionsargumente ist nach wie vor undefiniert, genauso wie der
exakte Zeitpunkt, wann Nebeneffekte dabei abgearbeitet werden.
Definiert ist lediglich, dass Nebeneffekte abgeschlossen sind, wenn
die Funktion die Kontrolle erhält.
foo( i++, i++ );
hat daher undefiniertes Verhalten.
Und nein: Dieses ',' hier ist nicht der Komma-Operator.
Grob gesagt kann man festhalten:
Der Compiler ist zwischen 2 Sequence-Points frei in seiner Entscheidung,
in welcher Reihenfolge er was abarbeiten möchte. Normalerweise hat ein
bestimmter Compiler eine bestimmte Auswertereihenfolge, die sich oft aus
den Eigenschaften der zugrundeliegenden Hardware ergibt. Zb werden oft
Funktionsargumente von rechts nach links ausgewertet, weil sie dann in
der richtigen Reihenfolge auf dem Stack abgelegt werden können.
Gefährlich wird es erst, wenn man implizit von einer bestimmten
Auswertereihenfolge ausgeht und das Programm von einem Compiler auf
einen anderen portiert wird, der eine andere Reihenfolge benutzt.
> if ( foo() && bar() ) {>> }>> if ( foo() || bar() ) {>> }>>> In beiden Fällen wird foo() zuerst bewertet.
Deshalb stellen in C das && und das || auch Sequenzpunkte dar.
> Dadurch lassen sich solche> Sachen formulieren:> Zeiger p;> if ( (p != NULL) && (p->bla) ) ..
Theretisch geht sogar das::
Achso, dabei am Rande ein Zitat:
Die Programmiersprache C. Ein Nachschlagewerk.
Regionales Rechenzentrum für Niedersachsen/Leibniz-Universität Hannover,
Zentralinstitut für angewandte Mathematik, Forschungsgruppe Jülich GmbH:
Dieses Institut hat so ein grünes Heftchen herausgebracht und freut
sich:
17. unveränderte Auflage, September 2008.
Auflabe: bisher 163.000 Exemplare.
Und in jedem dieser Exemplare steht auf Seite 77 unten:
A. K. schrieb:
> @Sven: Stimmt doch. Es ist möglich, dass "i" danach den Wert 3 hat. Es> ist natürlich auch möglich, dass "i" danach nicht den Wert 3 hat.
You made my day :-)
>>>> p && p->x++ || printf("Kein p\n");>>>> (zugegebenermaßen arg konstrukiert)>> Garnicht mal. Frag mal Perl-Leute, oder benutz ne anständige Konsole:> cc -o test test.c && ./test
Ist mir schon klar. In Shellskripten ist das recht üblich.
Aus einem Makefile, das ich letzte Woche schrieb:
Ich meinte, daß es für C eher konstruiert ist. Da ergibt sich bei dieser
Art der Verwendung von Operatoren nämlich das Problem, daß sich in C
nicht jeder Ausdruck als logischer Wert verwenden läßt. Das Beispiel
klappt nur, weil p->x ein int ist, printf einen int zurückgibt und p
sich in einen konvertieren läßt.
> i = ++i + 1; /* dies ist auch möglich; i hat Wert 3 */
Bevor ihr alle zu sehr über dieses Beispiel lästert: Kann jemand eine
Auswertereihenfolge angeben, bei der am Ende nicht 3 herauskommt? Mir
fällt spontan jedenfalls keine ein und bin deswegen fast überzeugt, dass
dieser Code nicht undefined ist.
"Fast" deswegen, weil ich noch nicht lange genug überlegt habe ;-)
> Mir fällt spontan jedenfalls keine ein und bin deswegen fast> überzeugt, dass dieser Code nicht undefined ist.
Nehme alles zurück, da exakt dieses Beispiel im ISO-Standard als
Beispiel für einen undefinierten Ausdruck angegeben ist :)
yalu schrieb:
>> i = ++i + 1; /* dies ist auch möglich; i hat Wert 3 */>> Bevor ihr alle zu sehr über dieses Beispiel lästert: Kann jemand eine> Auswertereihenfolge angeben, bei der am Ende nicht 3 herauskommt?
>> i = ++i + 1; /* dies ist auch möglich; i hat Wert 3 */>> Bevor ihr alle zu sehr über dieses Beispiel lästert: Kann jemand eine> Auswertereihenfolge angeben, bei der am Ende nicht 3 herauskommt?
Spielt eigentlich keine Rolle. Laut ISO-C braucht i danach gar keinen
Wert zu haben. Das Programm darf an der Stelle auch einfach
stehenbleiben oder danach beliebigen Blödsinn veranstalten. Nicht der
Wert gilt als undefiniert, sondern das gesamte Verhalten des Programms.
> Mir fällt spontan jedenfalls keine ein und bin deswegen fast überzeugt,> dass dieser Code nicht undefined ist.
Er könnte z.B. beim Operator++ den inkrementierten Wert zurückgeben, da
1 dazuzählen und an i zuweisen. Danach wird dann das vorher gemerkte
Ergebnis vom Operator++ nach i geschrieben. Dann steht danach 2 in i.
A. K. schrieb:
> temp = i + 1;> i = temp + 1;> i = temp;
Sven P. schrieb:
> zwischen = i + 1;> i = zwischen + 1;> i = zwischen;
@Sven P.: Danke für die Übersetzung des Codes von A. K. ins Deutsche.
Spätestens jetzt habe ich's wirklich kapiert ;-)
yalu schrieb:
>> i = ++i + 1; /* dies ist auch möglich; i hat Wert 3 */>> Bevor ihr alle zu sehr über dieses Beispiel lästert: Kann jemand eine> Auswertereihenfolge angeben, bei der am Ende nicht 3 herauskommt?
Du machst hier noch immer den Fehler, dass du ++i als eine Einheit
ansiehst, die 2 Aktion macht
i erhöhen
mit dem erhöhten Wert weiterrechnen
Tatsächlich ist die Sache aber etwas anders
* ++i
liefert den um 1 erhöhten Wert von i
das ist der Haupteffekt dieses Ausdrucks
* der Nebeneffekt dieses Ausdrucks ist es, dass dieser um 1 erhöhte
Wert wieder in i abgespeichert wird.
Nur ist in C nicht festgelegt, wann genau Nebeneffekte abgearbeitet
werden. Spätestens mit dem nächsten Sequence Point muss der Nebeneffekt
ausgeführt worden sein. Aber bis dorthin darf der Compiler den
Nebeneffekt schieben, wie er lustig ist.
Der Ausdruck ++i (oder i++) besteht also aus 2 Effekten, die zeitlich
nichts miteinander zu tun haben. Auch bei i++ hindert zb keine C-Regel
den Compiler daran, zunächst mit der Inkrementierung zu beginnen um
dann, wenn der Wert des Ausdrucks benötigt wird, von diesem
Zwischenergebnis wieder 1 abzuziehen. Theoretisch wäre das möglich, auch
wenn es natürlich kein Compiler so machen wird.
Fazit:
Wenn man eine bestimmte Reihenfolge der Auswertung erzwingen will (und
man hat nicht den Fall && ||) dann ist es am besten, selbst die
Anweisung in Teilanweisungen aufzusplitten. Dadurch erlangt man die
Kontrolle über die Auswertereihenfolge
result = foo() + bar();
vs
result = foo();
result += bar();
> Wenn ich mal ganz kleinlaut fragen darf, was stimmt mit dem Beispiel> nicht? ;)
Da stimmen zwei Sachen nicht. Eine in dem Teil, den du zitiert hast,
eine im anderen Teil.
getc() gibt einen Integer zurück oder ein Zeichen. (c = getc(..)) ist
wegen c vom Typ char. Dadurch kann EOF (-1) nicht mehr vom Zeichen #255
unterschieden werden.
Karl:
An deinem Beispiel ist mir grad nicht klar, was schief gehen soll --
Sven P. schrieb:
> getc() gibt einen Integer zurück oder ein Zeichen. (c = getc(..)) ist> wegen c vom Typ char. Dadurch kann EOF (-1) nicht mehr vom Zeichen #255> unterschieden werden.
AHA! Das hab ich doch glatt übersehen.
Sven P. schrieb:
> Karl:> An deinem Beispiel ist mir grad nicht klar, was schief gehen soll --
Ich könnte mir höchstens vorstellen, dass das EOF-Flag noch nicht
gesetzt ist, wenn eine leere Datei geöffnet wird. Und damit wird die
Schleife trotzdem einmal ausgeführt, obwohl nichts gelesen werden kann.
Zitat:
> This indicator is generally set by a previous operation on the stream that >
reached the End-of-File.
> Karl:> An deinem Beispiel ist mir grad nicht klar, was schief gehen soll --
feof(f) wird erst wahr, nachdem man versucht hat, über das Dateiende
hinaus zu lesen. Die Schleife wird also einmal zu oft durchlaufen.
Die Warnung selber ist eigentlich auch amüsant:
"warning: operation on `blubb' may be undefined"
Wieso "may be"? Gemäss Standard ist das Verhalten undefiniert.
Naja, der Standard sagt, es ist undefiniert. Das hindert aber niemanden,
der einen Compiler baut, es in irgendeiner Weise zu festzulegen. Im
Standard steht ja nicht 'must be undefined'.
Sven P. schrieb:
> Im> Standard steht ja nicht 'must be undefined'.
Genau, der Compiler muss in dem Fall, wenn er standardgemäß arbeiten
soll, eine zufällige binäre Sequenz in den Code einbauen ;)
> Wieso "may be"? Gemäss Standard ist das Verhalten undefiniert.
Ich nehme an, daß die Erkennung dieses Falls nicht 100% zuverlässig ist,
da sie doch schon ziemlich extensive Analyse des Codeflusses erfordert.
Es könnte also einen Fall geben, in dem die Warnung kommt, obwohl alles
in Ordnung ist. Deshalb "may be".
> Das hindert aber niemanden, der einen Compiler baut,> es in irgendeiner Weise zu festzulegen.
Und was hat er davon? Gar nichts, ausser zusätzlichen Implementations-
und Dokumentationsaufwand, weil der nächste Compiler folgende Variante
implementiert hat:
> Genau, der Compiler muss in dem Fall, wenn er standardgemäß arbeiten> soll, eine zufällige binäre Sequenz in den Code einbauen
So gesehen ist der Wortlaut "is undefined" das einzig Vernünftige. Wenns
mir mal langweilig genug ist, schreib ich einen Bugreport.
Bartli schrieb:
>> Das hindert aber niemanden, der einen Compiler baut,>> es in irgendeiner Weise zu festzulegen.>> Und was hat er davon? Gar nichts, ausser zusätzlichen Implementations-> und Dokumentationsaufwand, weil der nächste Compiler folgende Variante> implementiert hat:
Du kannst davon ausgehen, dass fast jeder Compiler es in irgendeiner
Weise definiert. Nur die allerwenigsten werden bei solch einer
Konstruktion einen Zufallsgenerator bemühen.
Ansonsten schließe ich mich Rolf Magnus an.
> Du kannst davon ausgehen, dass fast jeder Compiler es in irgendeiner> Weise definiert.
Nee, definitiv nicht. Aufgrund ihrer Implementation dürften die meisten
Compiler ein deterministisches Verhalten haben, aber du glaubst doch
nicht im Ernst das da jemand hingeht, sich zuerst überm ganzen Problem
den Kopf zerbricht und dann erst das Verhalten definiert und
dokumentiert?
Bartli schrieb:
>> Du kannst davon ausgehen, dass fast jeder Compiler es in irgendeiner>> Weise definiert.>> Nee, definitiv nicht. Aufgrund ihrer Implementation dürften die meisten> Compiler ein deterministisches Verhalten haben, aber du glaubst doch> nicht im Ernst das da jemand hingeht, sich zuerst überm ganzen Problem> den Kopf zerbricht und dann erst das Verhalten definiert und> dokumentiert?
Nö, hab ich das behauptet? Aber du siehst ja, die Java-Leute haben sich
zuerst den Kopf darüber zerbrochen und das Verhalten dann fest
definiert.
Laut C-Standard musst du auch damit rechnen, dass ein char 43 Bits hat,
davon aber 13 Bits nicht zum Wert gehören. Ein Zeiger ist dann 7 chars
breit und ein Funktionszeiger darf auch 5mal so groß sein, wie ein
Datenzeiger. Außerdem sind char, short, int und long alle gleichbreit.
Und? Das wäre eine vollkommen Standard-C-Konforme Umgebung.
Schön, Sven. Und jetzt?
Trotzdem glaube ich nicht, dass gcc's Implementation auf eine bewusst
definierte Art arbeitet, und die Formulation "may be" ist und bleibt
Quark.
Was anderes ist es im Fall von "blah may be used uninitialized", aber
darum gehts nicht, ebenso wenig um 47 Bits breite chars.
Und was Java betrifft: da gabs noch keine Altlasten in Form von
Compilern die lange vor irgendwelchen Standards existierten. Da hat sich
die Mühe das Zeugs vorwegs zu definieren sogar gelohnt.
> Laut C-Standard musst du auch damit rechnen, dass ein char 43 Bits hat,> davon aber 13 Bits nicht zum Wert gehören.
Nicht ganz. Bei char müssen alle Bits zum Wert beitragen.
> Ein Zeiger ist dann 7 chars breit und ein Funktionszeiger darf auch 5mal> so groß sein, wie ein Datenzeiger. Außerdem sind char, short, int und> long alle gleichbreit.>> Und? Das wäre eine vollkommen Standard-C-Konforme Umgebung.
Ja. Wenn das auf der Zielplattform sinnvoll wäre, warum auch nicht?