Forum: PC Hard- und Software Prüfungsfrage


von Student (Gast)


Lesenswert?

Hallo,

wir haben heute unsere Prüfung in "Grundlagen der Programmierung" 
gehabt.
Dabei tauchte folgende Aufgabe auf:
1
#include <stdio.h>
2
3
int main()
4
{  int *n;
5
  int m[2][3] ={{2,5,8},{4,8,9}};
6
  n-- = *(m+1)+2;
7
  printf("%d\n", *n);
8
  n--;
9
  printf("%d\n", *n);
10
}

Man sollte bestimmen, was das Programm ausgibt. Allerdings habe ich das 
gerade mit dem gcc getestet und festgestellt, dass man "n-- =" gar nicht 
machen kann. Hat einer von euch eventuell eine seriöse Begründung dafür? 
Oder noch besser einen Literatur Hinweis, wo etwas dazu steht?

Vielen Dank im Voraus :)

von Jonas K. (jonas_k)


Lesenswert?

Dass es ned kompilierbar is, ist wohl nicht die antwort. Machs halt so:
1
#include <stdio.h>
2
3
int main()
4
{  int *n;
5
  int m[2][3] ={{2,5,8},{4,8,9}};
6
  n = *(m+1)+2;
7
  n--; //Verhalten so wie urspr. gewünscht
8
  printf("%d\n", *n);
9
  n--;
10
  printf("%d\n", *n);
11
}

von Wolfgang (Gast)


Lesenswert?

Jonas K. schrieb:
> Machs halt so:

Formulierst du immer deine Aufgaben so um, wie es dir in den Kram paßt.

Student schrieb:
> Man sollte bestimmen, was das Programm ausgibt.

Klare Antwort: "nichts (weil es nicht kompilierbar ist)"
Dazu sollte man dann auf die entsprechende Stelle in der 
C-Sprachvereinbarung verweisen.

von KarlWurst (Gast)


Lesenswert?

Die Antwort ist "nichts", da das Programm Fehler enthält. Ihr habt ja 
einen lustigen Lehrer ;-).

Student schrieb:
> Hallo,
>
> wir haben heute unsere Prüfung in "Grundlagen der Programmierung"
> gehabt.
> Dabei tauchte folgende Aufgabe auf:
> ...
> Man sollte bestimmen, was das Programm ausgibt. Allerdings habe ich das
> gerade mit dem gcc getestet und festgestellt, dass man "n-- =" gar nicht
> machen kann. Hat einer von euch eventuell eine seriöse Begründung dafür?
> Oder noch besser einen Literatur Hinweis, wo etwas dazu steht?
>
> Vielen Dank im Voraus :)

von Bülent C. (mirki)


Lesenswert?

int i = 1;
i += i++ + ++i;

welchen wert hat i?? :-)

von Bülent C. (mirki)


Lesenswert?

Aber bitte jetzt keinen compiler anschmeissen

von xyz (Gast)


Lesenswert?

5?

von Bülent C. (mirki)


Lesenswert?

xyz schrieb:
> 5?

FALSCH

von Lutz H. (luhe)


Angehängte Dateien:

Lesenswert?

Was war die richtige Antwort?
bei mir sieht es wie im Bild aus, HABE DAS PROGRAMM ETWAS ERWEITERT.

von Lutz H. (luhe)


Lesenswert?

4?

von Stephan (Gast)


Lesenswert?

Ohne es probiert zu haben: 7

von karadur (Gast)


Lesenswert?

7 ist meine Antwort

von Bülent C. (mirki)


Lesenswert?

Jepp 7 ist richtig :-)

++C müste es doch heißen, weil C++ langsamer ist..... :-)

von casud (Gast)


Lesenswert?

7 ist auch falsch. Es ist schlichtweg nicht definiert.

von Peter II (Gast)


Lesenswert?

casud schrieb:
> 7 ist auch falsch. Es ist schlichtweg nicht definiert.

naja aber es kommt was raus - also gibt es maximale mehre antworten weil 
es mehre Compiler gibt.

von Bülent C. (mirki)


Lesenswert?

i ist ja 1, oder?!

i += 1 + ++i;    hier is i 2
i += 1 + 3;      und hier 3
i = 3 + 1 + 3;   Das muss man noch hinbekommen

von nocheinGast (Gast)


Lesenswert?

Und wenn ihr es noch 5x schreibt, dass angeblich 7 rauskommt... es 
bleibt undefiniert ^^.

von casud (Gast)


Lesenswert?

Nirgends im Standard steht geschrieben in welcher Reihenfolge die 
Expression ausgeführt wird.

clang, g++ und andere Compiler generieren hier auch schöne Warnungen.

von Lutz H. (luhe)


Lesenswert?

Irgendwie hat das was mit der definierten Berechnungsreihenfolge zu tun.

Bülent C. schrieb:
> i ist ja 1, oder?!

soweit ist es mir klar. was wird als nächtes berechnet?

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> i += i++ + ++i;

Es ist unklar, wann genau die Zuweisungen der beiden ++ Operatoren und 
des += Operators relativ zueinander ausgeführt werden.

So ist schon bei bei
  i += ++i;
nicht klar, ob das
  ++i;        // 2
  i += i;     // 4
oder
  temp = i+1; // 2
  i += temp;  // 3
  i = temp;   // 2
bedeutet.

von Bülent C. (mirki)


Lesenswert?

i++ und ++i übersetze ich mal in zwei Funktionen:

int ink_davor(int i) {
 i = i+1;
 return i;
}

i erhöht und der um 1 erhöhte Wert zurückgegeben



int ink_danach(int i) {
 i = i+1;
 return i-1;
}

i erhöht, jedoch der vorherige noch nicht um 1 erhöhte Wert 
zurückgegeben

von Amateur (Gast)


Lesenswert?

Ich würde sagen: Üben!

Keine Ahnung, ob Du ein wenig einfaches Abschreiben, oder euer "Leerer" 
etwas C trainieren sollte.

von nocheinGast (Gast)


Lesenswert?

lutz h. schrieb:
> was wird als nächtes berechnet?
Genau.

Das ist m. E. der relevante Satz:
> If a side effect on a scalar object is unsequenced relative
> to [...] a value computation using the value of the same
> scalar object, the behavior is undefined.
> [...]
> i = v[i++]; // the behavior is undefined
> i = i++ + 1; // the behavior is undefined

Sprich, wenn ein increment oder decrement an einer Variablen dran ist, 
dann darf sie im kompletten Ausdruck exakt dieses eine Mal vorkommen.
> int j = i++ + ++i;
ist genauso undefiniert.

von Bülent C. (mirki)


Lesenswert?

Also, bei ++i wird doch einfach inkremmentiert, richtig?
Und bei i++ wird ein temp. Objekt mit i erzeugt und i inkremmentiert und 
der temporäre Wert zurueckgegeben, richtig?

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Also, bei ++i wird doch einfach inkremmentiert, richtig?

++i ist die Kurzform von
  i += 1
und i += ... ist die Kurzform von
  i = i + ...

Also ist
  i += ++i
eigentlich
  i = i + (i = i + 1)

Wenn nun zwischen 2 Sequence Points (ggf. nachschlagen) mehr als eine 
Zuweisung auf die gleiche Variable erfolgt, dann ist undefiniert, wann 
genau welche davon erfolgt. Zudem ist hier undefiniert, ob die rechte 
Zuweisung vor oder nach dem Zugriff auf das "i" vor dem ersten "+" 
erfolgt.

: Bearbeitet durch User
von Max H. (hartl192)


Lesenswert?

Und was ist der Unterschied zwischen i++ und ++i?

von nocheinGast (Gast)


Lesenswert?

> The value of a postfix ++ expression is the value of its operand.
> [ Note: the value obtained is a copy of the original value —end note ]

> The operand of prefix ++ is modified by adding 1 [...] The result
> is the updated operand;

Hm, aber vielleicht kann ich dir einfacher kommen...
> i++ + ++i
müsste etwas anderes sein als
> ++i + i++
und das verletzt das Kommutativgesetz der Addition.

Es steht einfach nirgends, in welcher Reihenfolge ein Compiler einen 
Ausdruck auszuwerten hat, weil eben kein "sequence point" dazwischen 
ist. Der Compiler darf beispielsweise die Parameter eines 
Funktionsaufrufs auch in einer beliebigen Reihenfolge auswerten.

von (prx) A. K. (prx)


Lesenswert?

Max H. schrieb:
> Und was ist der Unterschied zwischen i++ und ++i?

  ++i
ist
  i = i + 1
  return i
und
  i++
ist
  temp = i
  i = i + 1
  return temp

von Max H. (hartl192)


Lesenswert?

A. K. schrieb:
> Max H. schrieb:
>> Und was ist der Unterschied zwischen i++ und ++i?
>
>   ++i
> ist
>   i = i + 1
>   return i
> und
>   i++
> ist
>   temp = i
>   i = i + 1
>   return temp

Danke.

von Amateur (Gast)


Lesenswert?

>Und was ist der Unterschied zwischen i++ und ++i?

pre- bzw. postinkrement

z.B.
int Nummer [ 3 ];
int i;

i = 0;
Nummer [ i++ ] := 5;       Bewirkt Nummer [ 0 ] = 5 | erst [i] dann + 1
Nummer [ i++ ] := 6;       Bewirkt Nummer [ 1 ] = 6 | erst [i] dann + 1

im Gegensatz zu:

i = 0;
Nummer [ ++i ] := 5;       Bewirkt Nummer [ 1 ] = 5 | erst + 1 dann [i]
Nummer [ ++i ] := 6;       Bewirkt Nummer [ 2 ] = 6 | erst + 1 dann [i]

oder so

von Lutz H. (luhe)


Lesenswert?

Bülent C. schrieb:
> int i = 1;
> i += i++ + ++i;

In meinem Buch steht,das zuerst ++ berechnet wird also:
i  +=  2 + 1;

von Amateur (Gast)


Lesenswert?

Sorry "=" statt ":="

von (prx) A. K. (prx)


Lesenswert?

lutz h. schrieb:
> In meinem Buch steht,das zuerst ++ berechnet wird also:
> i  +=  2 + 1;

Welches Buch?

von Egal (Gast)


Lesenswert?

A. K. schrieb:
> Max H. schrieb:
>> Und was ist der Unterschied zwischen i++ und ++i?
>
>   ++i
> ist
>   i = i + 1
>   return i
> und
>   i++
> ist
>   temp = i
>   i = i + 1
>   return temp

Das erklärt es meiner Meinung nach nicht vollständig.
1
    i = 0;
2
    printf ("%d\n", i++);

Ergibt als Ausgabe 0.
1
    i = 0;
2
    printf ("%d\n", ++i);

Ergibt als Ausgabe 1.

von Lutz H. (luhe)


Lesenswert?

Programmieren in C Kernigham/Ritchie

Vorrang und Reihenfolge bei Bewertungen

von (prx) A. K. (prx)


Lesenswert?

lutz h. schrieb:
> Programmieren in C Kernigham/Ritchie

Kannst die Stelle mal zitieren?

C99 führt beispielsweise explizit
  i = ++i + 1;
als undefiniert auf. War vorher auch schon so, aber in C99 lässt sich 
leichter suchen, weil PDF statt Papier.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Egal schrieb:
> Das erklärt es meiner Meinung nach nicht vollständig.

Inwiefern?

>     i = 0;
>     printf ("%d\n", i++);

int temp;
printf(..., (temp = i /*=0*/, i = i + 1, temp /*=0*/))

>     printf ("%d\n", ++i);

printf(..., i = i + 1 /*=1*/);

von Student (Gast)


Lesenswert?

Falls das falsch verstanden wurde, Die Aufgabe war nicht zum abtippen.
Ich musste die in einer realen Prüfung auf Papier lösen. Ohne Computer.

von (prx) A. K. (prx)


Lesenswert?

Student schrieb:
> Falls das falsch verstanden wurde, Die Aufgabe war nicht zum abtippen.
> Ich musste die in einer realen Prüfung auf Papier lösen. Ohne Computer.

Das war schon klar. Es ändert aber nichts daran, dass n-- keine Lvalue 
ist, also nicht links von einer Zuweisung stehen darf. Der gezeigte Code 
läuft folglich auf eine Fehlermeldung des Compilers, wenn der genau und 
nur Standard-C verdaut.

Die Frage ist also nicht beantwortbar, da es kein ausführbares Programm 
geben kann. Es ist zwar vermutlich das gemeint, was Jonas schrieb, aber 
wenn du in einer realen Prüfung auf eine solche Frage stösst, und es 
keine Fangfrage ist, dann sitzt du ziemlich in der Tinte.

: Bearbeitet durch User
von nocheinGast (Gast)


Lesenswert?

Du willst eine seriöse Quelle? :)
Nur l-Values können Ziel einer Zuweisung sein, wie der gcc korrekt 
feststellt:
> test.cpp:6:7: error: lvalue required as left operand of assignment
>    n-- = *(m+1)+2;
>        ^

Und der C++-Standard sagt zu "--":
> 5.2.6 Increment and decrement [expr.post.incr]
> [...] The operand shall be a modifiable lvalue.
> [...] The result is a prvalue.
> [...]

Ein pr-Value ist nunmal kein l-Value.

von Student (Gast)


Lesenswert?

Danke

von lalala (Gast)


Lesenswert?

Hast Du den Aufgabenzettel noch, oder machst Du das aus der Erinnerung?
Selbst wenn es kompiliert greift es willkürlich auf den Speicher zu.

von (prx) A. K. (prx)


Lesenswert?

lalala schrieb:
> Selbst wenn es kompiliert greift es willkürlich auf den Speicher zu.

Weshalb? Wenn man das n-- wegschiebt, wie Jonas zeigte, dann zeigt n 
korrekt auf Elemente von m.

: Bearbeitet durch User
von Dr.ING (Gast)


Lesenswert?

In solchen Fällen würde ich schreiben, dass
1.) die Originalaufgabe nicht kompilierbar ist, weil...

2.) nach Anpassung an die C-Sprachvereinbarung 8 und 4 angezeigt wird.

Fertig.
Lösungsorientiertes arbeiten.

von Bülent C. (mirki)


Lesenswert?

Letzendlich wird immer das wiederholt was ich schon mehrfach geschrieben 
hatte..... Unterschied zwischen ++i und i++ und noch ein paar andere 
Sachen fallen dadrunter


#include<stdio.h>

int main() {
  int i = 1;
  i += i++ + ++i;
  printf ("Wert von i: %d\n", i);
  return 0;
}


Mir wurde bis dato nicht schlüßig erklärt, warum das nicht funktionieren 
sollte...Also rein theoretisch und auch praktisch ist i=7!

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

Bülent C. schrieb:
> Mir wurde bis dato nicht schlüßig erklärt, warum das nicht funktionieren
> sollte...Also rein theoretisch und auch praktisch ist i=7!

i += i++ + ++i;

weil C nur festlegt das das ++ beim ; erledigt sein muss. Es ist also 
genauso zulässig das i sich für das ++i noch nicht geändert hat also 
immer noch 1 ist.

von Bülent C. (mirki)


Lesenswert?

Peter II schrieb:
> Bülent C. schrieb:
>> Mir wurde bis dato nicht schlüßig erklärt, warum das nicht funktionieren
>> sollte...Also rein theoretisch und auch praktisch ist i=7!
>
> i += i++ + ++i;
>
> weil C nur festlegt das das ++ beim ; erledigt sein muss. Es ist also
> genauso zulässig das i sich für das ++i noch nicht geändert hat also
> immer noch 1 ist.

Resultat?

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Resultat?

Wie wärs mit 2?
  temp = i + 1; // erste Operation
  ...uninteressantes Zeugs...
  i = temp;     // letzte Operation

Ein reales Ergebnis eines realen Compilers ist kein Beweis dafür, dass 
eine Operation definiert ist.

Die Warnungen eines realen Compilers wie GCC sind allerdings ein 
ziemlich deutliches Indiz dafür, dass das Ergebnis qua Standard 
undefiniert ist.

Vielleicht hilft es dir, dass der C Standard
  i = ++i + 1;
ausdrücklich als undefiniert erklärt. Dieses Statement steht explizit 
bei C99 als Beispiel dafür drin.

: Bearbeitet durch User
von Bülent C. (mirki)


Lesenswert?

A. K. schrieb:
> Bülent C. schrieb:
>> Resultat?
>
> Wie wärs mit 2?
>   temp = i + 1; // erste Operation
>   ...uninteressantes Zeugs...
>   i = temp;     // letzte Operation
>
> Ein reales Ergebnis eines realen Compilers ist kein Beweis dafür, dass
> eine Operation definiert ist.
>
> Die Warnungen eines realen Compilers wie GCC sind allerdings ein
> ziemlich deutliches Indiz dafür, dass das Ergebnis qua Standard
> undefiniert ist.
>
> Vielleicht hilft es dir, dass der C Standard
>   i = ++i + 1;
> ausdrücklich als undefiniert erklärt. Dieses Statement steht explizit
> bei C99 als Beispiel dafür drin.

Sorry, aber ich habe immer noch keinen Beleg dafür, warum es nicht 
definierbar sein soll.
Vielleicht noch mal als Anregung.... ++i inkrementiert nur....i++ 
erzeugt ein temporäres Object mit i und i inkremmentiert und
der temporäre Wert zurueckgegeben.
Spielt hier denn nich eine Reihenfolge eine Rolle? Zb eine art 
Evaluationsreihenfolge?

Unabhängig davon...mein gcc gibt nicht mal ne Warnmeldung aus und spuckt 
als Ergebnis ne 7 aus.

von (prx) A. K. (prx)


Angehängte Dateien:

Lesenswert?

Ich hoffen das reicht. Quelle C99 draft n1256.

> Sorry, aber ich habe immer noch keinen Beleg dafür, warum es nicht
> definierbar sein soll.

Falsche Frage. Es wäre sicherlich irgendwie definierbar, es ist im C 
Standard aber nicht definiert.

: Bearbeitet durch User
von Bülent C. (mirki)


Lesenswert?

A. K. schrieb:
> Ich hoffen das reicht. Quelle C99 draft n1256.

Schaumal was zur Fußnote 72 steht ;-)

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

Was Gefällt Dir nicht an meinem letzten comment:
++i inkrementiert nur....i++
erzeugt ein temporäres Object mit i und i inkremmentiert und
der temporäre Wert zurueckgegeben.
Spielt hier denn nich eine Reihenfolge eine Rolle? Zb eine art
Evaluationsreihenfolge?

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Schaumal was zur Fußnote 72 steht ;-)

Da steht "A floating-point status flag is not an object and can be set 
more than once within an expression."

Und?

> Was Gefällt Dir nicht an meinem letzten comment:

Dass es völlig egal ist, was du von C erwartest und was C deiner Ansicht 
nach tun sollte. Entscheidend ist, was der Standard definiert.

In deinem Statement wird i dreimal modifiziert. Und nun lies nochmal, 
was der Standard schreibt: "Between the previous and next sequence point 
an object shall have its stored value modified at most once by the 
evaluation of an expression."

> Spielt hier denn nich eine Reihenfolge eine Rolle? Zb eine art
> Evaluationsreihenfolge?

Es gibt Sequence Points, aber in diesem Statement ist keiner. Nur davor 
und danach, aber nicht darin.

: Bearbeitet durch User
von chris (Gast)


Lesenswert?

Hallo zusammen,

int i = 1;
i += i++ + ++i;

in diesem Beispiel kommt tatsächlich IMMER 7 raus, allerdings kann das 
auf zwei Arten berechnet werden:

Auswertereihenfolge der Operatoren:
'+=' wird zuletzt ausgewertet
'+' wird als vorletztes ausgewertet und bei
i++ und ++i ist die Reihenfolge undefiniert, in jedem Fall werden beide
vor dem '+' ausgewertet. Also

1. Möglichkeit i++ wird als erstes ausgewertet:
i++ hat den Wert 1, i ist nach der Auswertung 2
++i hat dann den Wert 3, i ist ebenfalls 3
Damit wird die ganze Rechnung zu i = 3 + (1 + 3) und damit zu 7

2. Möglichkeit ++i wird als erstes ausgewertet:
++i hat den Wert 2, i ist nach der Auswertung ebenfalls 2
i++ hat dann den Wert 2, i ist danach 3
Damit wird die ganze Rechnung zu i = 3 + (2 + 2) und damit ebenfalls 7

von Bülent C. (mirki)


Lesenswert?

A. K. schrieb:
>
> Dass es völlig egal ist, was du von C erwartest und was C deiner Ansicht
> nach tun sollte. Entscheidend ist, was der Standard definiert.
>
> In deinem Statement wird i dreimal modifiziert. Und nun lies nochmal,
> was der Standard schreibt: "Between the previous and next sequence point
> an object shall have its stored value modified at most once by the
> evaluation of an expression."
>
> Wenn dir das immer noch nicht reicht, dann definiere dein eigenes
> "Bülent C", aber behaupte nicht, das wäre "Standard C".
>
>> Spielt hier denn nich eine Reihenfolge eine Rolle? Zb eine art
>> Evaluationsreihenfolge?
>
> Es gibt Sequence Points, aber in diesem Statement ist keiner. Nur davor
> und danach, aber nicht darin.
Du änderst Deinen Beitrag mehrfach in kürzester Zeit! Ist das Dein 
Standart zu debattieren?

Und außerdem gehst Du auf einen wichtigen Aspekt nicht ein und ehrlich 
gesagt habe ich auch keine Lust mehr mich mit Dir zu beschäftigen.

von (prx) A. K. (prx)


Lesenswert?

@chris: Da es sich eindeutig um "undefined behaviour" handelt, darf auch 
-9999 rauskommen. Oder ein Absturz.

: Bearbeitet durch User
von Bülent C. (mirki)


Lesenswert?

A. K. schrieb:
> @chris: Da es sich eindeutig um "undefined behaviour" handelt,
> darf auch
> -9999 rauskommen. Oder ein Absturz.

QUATSCH!

von Garden (Gast)


Lesenswert?


von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> A. K. schrieb:
>> @chris: Da es sich eindeutig um "undefined behaviour" handelt,
>> darf auch
>> -9999 rauskommen. Oder ein Absturz.
>
> QUATSCH!

"Possible undefined behavior ranges from ignoring the situation 
completely with unpredictable results, to behaving during translation or 
program execution in a documented manner characteristic of the 
environment (with or without the issuance of a diagnostic message), to 
terminating a translation or execution (with the issuance of a 
diagnostic message)."

GCC ist in neueren Versionen dafür berüchtigt geworden, undefiniertes 
Verhalten kreativ zu seinen Gunsten auszulegen. Das hat schon manche 
überrascht.

von casud (Gast)


Lesenswert?

'+=' wird zwar zuletzt ausgewertet. Dennoch kann der Compiler 
theoretisch eine temporäre Variable erzeugen, womit das Ergebnis auch 4 
oder 5 oder auch was ganz anderes sein kann.

gcc: (mit -Wall)
test1.c: In function ‘main’:
test1.c:6:7: warning: operation on ‘i’ may be undefined 
[-Wsequence-point]
test1.c:6:7: warning: operation on ‘i’ may be undefined 
[-Wsequence-point]

clang: (funktioniert wohl erst ab 3.3 oder sogar 3.4)
test1.c:6:11: warning: unsequenced modification and access to 'i'
      [-Wunsequenced]
    i += i++ + ++i;
      ~~  ^
1 warning generated.

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo,

chris schrieb:
> Hallo zusammen,
>
> int i = 1;
> i += i++ + ++i;

> Auswertereihenfolge der Operatoren:

> '+' wird als vorletztes ausgewertet und bei
> i++ und ++i ist die Reihenfolge undefiniert, in jedem Fall werden beide
> vor dem '+' ausgewertet. Also

beide vor dem +? Echt? Warum denn solches? Hätte ich nicht gedacht.
Also in C++ würde

    int i = 1;
    std::cout << i++ + 1;

Die Ausgabe 2 erzeugen,
also das ++ würde natürlich nach dem + ausgewertet. Weicht C an der 
Stelle ab?
Das wäre doch sehr seltsam!

vlg
Timm

P.S. Bei mir ergibt schon das bloße Eintippen von
i += i++ + ++i; eine gelbe Unterlegung mit Warndreieck und
den Hinweis: Unsequenced modification and access to 'i'

von (prx) A. K. (prx)


Lesenswert?

Hier war auch schon jemand über undefiniertes Verhalten gestolpert. Das 
war einer jener Fälle, wo der Compiler zur Überraschung einiger Leute 
das recht kreativ ausnutzt: Beitrag "Hilfe - AVR-GCC "optimiert" Schleife zur Endlosschleife"

: Bearbeitet durch User
von Udo S. (urschmitt)


Lesenswert?

A. K. schrieb:
> Hier war auch schon jemand über undefiniertes Verhalten gestolpert.

In meinem ersten Job sind wir auch mal über so ein undefiniertes 
Verhalten gestossen. War ein ziemlich komplexer legacy Code eines 
Geldautomaten, Auswertung des Authorisierungscodes von der 
Authorisierungszentrale
War ungefähr so:

unsigned char[3] answer;
receive(answer);
if ((answer[i++] == '0') && (answer[i++]))
  return ok;
else
  ...

hatte in MSC 5.X einwandfrei funktioniert, beim Umstieg auf MSC 6.0 
wurde leider zweimal das erste Zeichen ausgewertet.
Der Code für Karte gesperrt war "04" Die Auswertung sah "00", das hies 
alles OK, auszahlen.

Bis die Bank es gemerkt hatte waren irgendwas zwischen 20000 und 100000 
DM weg!
Kein Witz.

von Matthias (Gast)


Lesenswert?

Ohne alles genau durchgelesen zu haben:
1
#include <stdio.h>
2
3
int main()
4
{  int *n;
5
  int m[2][3] ={{2,5,8},{4,8,9}};
6
  n-- = *(m+1)+2;
7
  printf("%d\n", *n);
8
  n--;
9
  printf("%d\n", *n);
10
}

In dem Programm geht es nicht um Operator-Vorrang, weil *n-- = *(m+1)+2; 
funktioniert ja. Genauso wie n = *(m+1)+2; geht und anschließend 
dekrementieren.

>>Es ändert aber nichts daran, dass n-- keine Lvalue
>>ist, also nicht links von einer Zuweisung stehen darf.

Wieso lässt er das nicht durchgehen wie im obigen Beispiel bzw was ist 
der Sinn dahinter dass dies vom Standard verboten wird? Eigentlich ja 
daran nichts ungewöhnliches.

von Peter II (Gast)


Lesenswert?

Matthias schrieb:
>>>Es ändert aber nichts daran, dass n-- keine Lvalue
>>>ist, also nicht links von einer Zuweisung stehen darf.
>
> Wieso lässt er das nicht durchgehen wie im obigen Beispiel bzw was ist
> der Sinn dahinter dass dies vom Standard verboten wird? Eigentlich ja
> daran nichts ungewöhnliches.

was soll das für einen sinn machen.

n-1 = 5;

das ist nun mal keine Zuweisung

von Yalu X. (yalu) (Moderator)


Lesenswert?

Hier ist ein Beispiel, wo sich zwei unterschiedliche Compiler
unterschiedlich undefiniert verhalten:
1
#include <stdio.h>
2
3
int main () {
4
  int i, j; 
5
  j = (i=1) + (i=2);
6
  printf("%d\n", j);
7
  return 0;
8
}

GCC:
1
4

Clang:
1
3

Welcher hat wohl recht?

Beide.


Udo Schmitt schrieb:
> if ((answer[i++] == '0') && (answer[i++]))
>   return ok;
> else
>   ...
>
> hatte in MSC 5.X einwandfrei funktioniert, beim Umstieg auf MSC 6.0
> wurde leider zweimal das erste Zeichen ausgewertet.

Wenn ich mich nicht täusche, ist das aber ein Fehler des Compilers (MSC
6.0) und nicht des Programmierers, denn

  "Unlike the bitwise binary & operator, the && operator guarantees
  left-to-right evaluation; there is a sequence point after the
  evaluation of the first operand."

> Bis die Bank es gemerkt hatte waren irgendwas zwischen 20000 und 100000
> DM weg!

Geld von MS zurückfordern :)

von Bülent C. (mirki)


Lesenswert?

OK, es ist ein "undefined behaviour".... gebe mich zu diesem Punkt 
geschlagen....

Warum kommt aber immer 7 raus?

Wenn ich anstelle:

  int i = 1;
  i += i++ + ++i;

dieses nehme
  int i = 1;
  i += ++i + i++;

bekomme ich immer 8 raus...

Da hier zwischen den beiden Statements unterschiedle Ergebnise geliefert 
werden, aber jeweils immer die gleichen, kann man doch die Annahme 
treffen, das es eine Reihenfolge bzw. eine Priorisierung festgelegt 
wird.
Folglich scheint erst das i++ dann ++i ausgewertet und dann zusammen 
addiert und i hinzugefügt.
Basierend auf was wird eine Reihenfolge festgelegt?
Oder will mir jemand ein "irrationales Verhalten" des compilers 
Beweisen?!

von Udo S. (urschmitt)


Lesenswert?

Yalu X. schrieb:
> Geld von MS zurückfordern :)

Der Schaden hat sich meines Wissens Bank/Versicherung und mein 
Unternehmen zu gleichen Teilen geteilt, weis ich aber nicht genau.
Auf jeden Fall gab es damals ordentlich Stunk. Problem war, das war Code 
zur Authorisierung mit einer amerikanischen Kreditkartenfirma. Wenn man 
mit der testen wollte musste man
- eine funktionierende Testkarte haben
- das Testsystem in USA zum Laufen bringen (viel telefon)
- das Testsystem der Europazentrale (London) zum Laufen bringen (noch 
mehr Telefon)
- das Testsystem der Deutschlandzentrale zum Laufen bringen (...)

Und die Person, die das immer gemacht hatte und die Ansprechpartner 
kannte, war gerade ein paar Wochen weg (hatte bessern Job gefunden)

Das war alles noch im pre-Internet Zeitalter mit X25 / Datex-P

War schon spannend .-)

von (prx) A. K. (prx)


Lesenswert?

Udo Schmitt schrieb:
> if ((answer[i++] == '0') && (answer[i++]))
>   return ok;

Das allerdings ist definiert, da "&&" ein Sequence Point ist und 
folglich dessen rechte Seite von einem aktualisierten i ausgehen darf.

von Udo S. (urschmitt)


Lesenswert?

A. K. schrieb:
> Das allerdings ist definiert, da "&&" ein Sequence Point ist und
> folglich dessen rechte Seite von einem aktualisierten i ausgehen darf.

Jepp, ich meine ich hätte mit Karl Heinz auch mal darüber diskutiert. 
Damals wusste ich aber noch nichts von Sequence Points und Microsoft 
wohl auch noch nicht so richtig.

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Warum kommt aber immer 7 raus?

Wenn alles rauskommen darf, dann ist 7 doch nicht falsch. Compiler sind 
nicht dazu verpflichtet, bei undefiniertem Verhalten eine Bombe zu 
zünden. Sie nicht einmal dazu verpflichtet, eine Warning zu liefern, und 
dennoch tut gcc das hier (-Wall).

> Da hier zwischen den beiden Statements unterschiedle Ergebnise geliefert
> werden, aber jeweils immer die gleichen, kann man doch die Annahme
> treffen, das es eine Reihenfolge bzw. eine Priorisierung festgelegt
> wird.

Nein.

> Basierend auf was wird eine Reihenfolge festgelegt?

Wie es sich aus der inneren Arbeitsweise des Compilers eben ergibt. Hier 
kommt immer das gleiche raus. In anderen Beispielen nicht.

von Peter II (Gast)


Lesenswert?

Bülent C. schrieb:
> Basierend auf was wird eine Reihenfolge festgelegt?

Auf Basis vom Quellcode des GCC. Es kann sich aber in der nächsten 
Version wieder ändern. Im schlimmsten fall ist es sogar abhängig von den 
Speicheradresse der internen GCC Strukturen - ist aber auch egal. Das 
das verhalten nicht definiert ist jede Lösung richtig.

von (prx) A. K. (prx)


Lesenswert?

Matthias schrieb:
> Wieso lässt er das nicht durchgehen wie im obigen Beispiel bzw was ist
> der Sinn dahinter dass dies vom Standard verboten wird? Eigentlich ja
> daran nichts ungewöhnliches.

Wieso wurde in C die Zuweisung mit = definiert, statt über := die 
Verwechslungen wie beim Klassiker "if (i = 2)" zu vermeiden? Es wurde 
vom Standard nun einmal definiert, dass n-- eine Rvalue ist und keine 
Lvalue. Und Dennis Ritchie kannst du leider nicht mehr fragen.

Besonders sinnvoll finde ich
   n-- = 0;
allerdings auch nicht wirklich.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Matthias schrieb:
> weil *n-- = *(m+1)+2; funktioniert ja.

Nein, ist Typenfehler.

: Bearbeitet durch User
von casud (Gast)


Lesenswert?

Bülent C. schrieb:
> Wenn ich anstelle: ..
> dieses nehme
>   int i = 1;
>   i += ++i + i++;
>
> bekomme ich immer 8 raus...

Hier bekomme ich zum Beispiel 7 raus und schon schnappt die Falle zu...

von Bülent C. (mirki)


Lesenswert?

casud schrieb:
> Bülent C. schrieb:
>> Wenn ich anstelle: ..
>> dieses nehme
>>   int i = 1;
>>   i += ++i + i++;
>>
>> bekomme ich immer 8 raus...
>
> Hier bekomme ich zum Beispiel 7 raus und schon schnappt die Falle zu...

Was für 'ne Falle?? Das der Compiler Irrational handelt oder wie?...
Ist schon klar das es ein undefiniertes Verhalten ist, aber mit dem 
gleichen Compiler kommen bei beidem unterschiedliche Werte raus....

von Bülent C. (mirki)


Lesenswert?

Peter II schrieb:
> Bülent C. schrieb:
>> Basierend auf was wird eine Reihenfolge festgelegt?
>
> Auf Basis vom Quellcode des GCC. Es kann sich aber in der nächsten
> Version wieder ändern. Im schlimmsten fall ist es sogar abhängig von den
> Speicheradresse der internen GCC Strukturen - ist aber auch egal. Das
> das verhalten nicht definiert ist jede Lösung richtig.

Das hört sich sinnvoll an.

von Peter II (Gast)


Lesenswert?

Bülent C. schrieb:
> aber mit dem
> gleichen Compiler kommen bei beidem unterschiedliche Werte raus....
es gibt ja noch verschiede Versionen vom gleichen Compiler und auch noch 
verschiedene Plattformen wo er läuft. Auch die Optimierung kann das 
Ergebnis verändern.

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Was für 'ne Falle??

Die Falle, sich bei Code mit gemäss Standard undefiniertem Verhalten auf 
ein bestimmtes Verhalten des realen Compilers zu verlassen, "weil es 
doch schon immer so war".

So dürfte auch der Spass mit der vorhin verlinkten Totschleife bei 
vielen Compilern so ausgehen, wie der Programmierer sich das gedacht 
hatte. Wohl auch in den meisten Vorversionen von GCC. Aber irgendwann...

Es stolpern auch immer und immer wieder Leute darüber, dass sie abhängig 
von der Optimierungsstufe ein anderes Ergebnis erhalten. Das ist 
überhaupt nicht irrational, sofern das Programm formal korrekt ist.

: Bearbeitet durch User
von Matthias (Gast)


Lesenswert?

A. K. schrieb:
> Matthias schrieb:
>> weil *n-- = *(m+1)+2; funktioniert ja.
>
> Nein, ist Typenfehler.

wieso? n ist ein pointer auf int.

von (prx) A. K. (prx)


Lesenswert?

Matthias schrieb:
> wieso? n ist ein pointer auf int.

Ja, aber die rechte Seite ist dies auch, *n aber nicht. Folglich geht
  n = ...
aber nicht
  *n = ...

: Bearbeitet durch User
von Matthias (Gast)


Lesenswert?

Aja stimmt, weil m mehrdimensional ist, wenn ich mich nicht wieder 
täusche...

von Martin (Gast)


Lesenswert?

int i;
int f(){i=1; return i;}
int g(){i=2; return i;}
i = f()+g();
=> undefined behaviuor ... ohne lange zu überlegen
kein Sequenzpunkt bei Mehrfachzuweisung an i

übrigens auch die Reihenfolge der Aufrufe f und g
sind nicht definiert. Jetzt breche ich eine weitere Diskussion
mit mirki :)

in Java ist meines Wissens Ergebnis der Mehrfachzuweisungen definiert.
Man hat sich schlicht gesagt .. mach erst präinc, dann postinc
in C hat man sich gesagt, Compiler ist frei seine
"process chunks" zu-zuschneiden. Er ist auch frei f und g Aufrufe
gleichzeitig auf multicore ablaufen zu lassen ... so what
in Java wird's wahrscheinlich erst f, dann g aufgerufen

von Bülent C. (mirki)


Lesenswert?

Martin schrieb:
> übrigens auch die Reihenfolge der Aufrufe f und g
> sind nicht definiert. Jetzt breche ich eine weitere Diskussion
> mit mirki :)

Wenn eine Reihenfolge laut Definition nicht festgelegt ist, aber 
anscheinend doch eine Reihenfolge vom Complier eingehalten wird, was ich 
ja schon bewiesen habe, dann scheint dieser nach Deiner Auffasung eine 
Willkürliche Wahl des Compilers zu sein?!
Undefiniert für den Compiler würde bedeuten, das jedesmal ein anderes 
Ergebnis rauskommen würde, tut es aber nicht beim gleichen Compiler.

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Undefiniert für den Compiler würde bedeuten, das jedesmal ein anderes
> Ergebnis rauskommen würde, tut es aber nicht beim gleichen Compiler.

Was du schreibst würde wiederum eine Definition voraussetzen. Nämlich 
die, dass jedes Mal etwas anders heraus kommen müsse. Aber auch diese 
Definition gibt es nicht, so dass auch jedes Mal das gleiche Ergebnis 
rauskommen darf. ;-)

Ernst beiseite: Der Begriff "undefiniert" bedeutet nur, dass der 
Standard kein bestimmtes Ergebnis definiert und schlicht völlig offen 
lässt, was passiert. Unspezifiziert hiesse, dass es eine Regel gibt, die 
aber der Implementierung überlassen ist und darin offengelegt wird. 
Undefiniert heisst auch, dass nicht definiert ist ob es eine Regel gibt 
oder nicht, und dies auch niemand zu erfahren braucht.

: Bearbeitet durch User
von Walter (Gast)


Lesenswert?

Bülent C. schrieb:
> Wenn eine Reihenfolge laut Definition nicht festgelegt ist, aber
> anscheinend doch eine Reihenfolge vom Complier eingehalten wird, was ich
> ja schon bewiesen habe,

d.h. Du hast bewiesen dass anscheinend eine Reihenfolge eingeahlten 
wird?
In der Mathematik und Logik laufen Beweise anders ab

von Bülent C. (mirki)


Lesenswert?

Walter schrieb:
> d.h. Du hast bewiesen dass anscheinend eine Reihenfolge eingeahlten
> wird?
> In der Mathematik und Logik laufen Beweise anders ab

Solange Du mir nicht das Gegenteil beweist, dann ist mein Beweis gültig, 
auch wenn dieser nur auf einen Compiler Typen beschränkt ist.

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Solange Du mir nicht das Gegenteil beweist, dann ist mein Beweis gültig,
> auch wenn dieser nur auf einen Compiler Typen beschränkt ist.

Eine Behauptung wird nicht schon dadurch zum Beweis, dass sich niemand 
bemüssig fühlt, das Gegenteil zu beweisen.

Der Begriff "undefiniert" schliesst nicht aus, dass sich Programme auch 
in dem dadurch umrissenen Gebiet nach Regeln verhalten. Tatsächlich 
geschieht das sogar ziemlich häufig. Wenn du dich auf solche Regeln 
einlässt, dann programmierst du allerdings nicht in "ANSI C", sondern in 
"Bülent C".

von Bülent C. (mirki)


Lesenswert?

A. K. schrieb:
> Der Begriff "undefiniert" schliesst nicht aus, dass sich Programme auch
> in dem dadurch umrissenen Gebiet nach Regeln verhalten.

Das ist doch ein Widerspruch in sich...Kann man sich einigen auf : Im 
Standart sprec ist es undefiniert aber im Compiler exisitiert eine 
Reihenfolgen Definition?

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Das ist doch ein Widerspruch in sich...

Nein, keineswegs.

> Kann man sich einigen auf : Im
> Standart sprec ist es undefiniert aber im Compiler exisitiert eine
> Reihenfolgen Definition?

Nein. Das wird zwar meist so sein, ist aber nicht definiert. Ausserdem 
kennst du diese Definition nicht wirklich genau, selbst wenn sie 
existiert.

Im Übrigen solltest du bedenken, dass bei der Ausseinandersetzung 
zwischen Compilern und Menschen der Compiler praktisch immer sturer ist. 
Kein Anwender ist so stur wie ein Compiler, auch wenn er sich noch so 
anstrengt. Da kannst du regalmeterweise Beschwörungen runterbeten und 
Behauptungen zu Beweisen erklären. Solange du nicht zu seinen Herren und 
Meistern gehörst, also jenen, die ihn verstehen und an ihm rumschreiben 
können, bleibt er stets der Sieger.

: Bearbeitet durch User
von Lutz H. (luhe)


Lesenswert?

A. K. schrieb:
> Kannst die Stelle mal zitieren?

Hier habe ich die Tabelle gefunden, in der die Reihenfolge beschrieben 
wird.
Ist doch etwas groß zum Abschreiben.



http://www.ostc.de/c-precedence.pdf

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

lutz h. schrieb:
> Hier habe ich die Tabelle gefunden, in der die Reihenfolge abgearbeitet
> wird.

Die operator precedence hilft dir hier nicht weiter. Die Tatsache, dass 
die Addition linksassoziativ ist führt nicht dazu, dass in f()+g() 
zuerst f und dann g aufgerufen wird.

von Walter (Gast)


Lesenswert?

Bülent C. schrieb:
> Walter schrieb:
>> d.h. Du hast bewiesen dass anscheinend eine Reihenfolge eingeahlten
>> wird?
>> In der Mathematik und Logik laufen Beweise anders ab
>
> Solange Du mir nicht das Gegenteil beweist, dann ist mein Beweis gültig,
> auch wenn dieser nur auf einen Compiler Typen beschränkt ist.

dann habe ich auch "bewiesen" dass MS-Word fehlerfrei ist denn ich habe 
damit schon 2 Texte geschrieben und es hat funktioniert.

von Bülent C. (mirki)


Lesenswert?

A. K. schrieb:
> Im Übrigen solltest du bedenken, dass bei der Ausseinandersetzung
> zwischen Compilern und Menschen der Compiler praktisch immer sturer ist.
> Kein Anwender ist so stur wie ein Compiler, auch wenn er sich noch so
> anstrengt. Da kannst du regalmeterweise Beschwörungen runterbeten und
> Behauptungen zu Beweisen erklären. Solange du nicht zu seinen Herren und
> Meistern gehörst, also jenen, die ihn verstehen und an ihm rumschreiben
> können, bleibt er stets der Sieger.

Kniest Du vor Deinem Compiler nieder, weil er etwas machst was ihm nicht 
vorgegeben wurde?

von (prx) A. K. (prx)


Lesenswert?

lutz h. schrieb:
> Hier habe ich die Tabelle gefunden, in der die Reihenfolge beschrieben
> wird. Ist doch etwas groß zum Abschreiben.

PS: Bei f() + g() ist die Reihenfolge des Aufrufs offen. Bei f() && g() 
hingegen nicht. Denn "&&" ist ein sequence point, "+" nicht.

von (prx) A. K. (prx)


Lesenswert?

Bülent C. schrieb:
> Kniest Du vor Deinem Compiler nieder, weil er etwas machst was ihm nicht
> vorgegeben wurde?

Kommt drauf an welcher, also ob ich sein Herr und Meister bin (das ist 
freilich schon eine Weile her), oder nur sein Anwender. Wobei ich aber 
auch GCC schon mal auf die Sprünge half, als mir sein Ergebnis nicht 
passte.

Im Regelfall bleibt einem freilich keine Alternative, als vor ihm nieder 
zu knien und ihm zu folgen, wenn man zu einem Ergebnis kommen will. 
Andere Herren und Meister zu beschwören (etwa via Bugzilla) dauert meist 
zu lang.

Wie ist dein Ansatz? Ihn so lange zu verfluchen bis er dich erhöhrt?

: Bearbeitet durch User
von Martin (Gast)


Lesenswert?

Bülent C. schrieb:
> Das ist doch ein Widerspruch in sich...Kann man sich einigen auf : Im
> Standart sprec ist es undefiniert aber im Compiler exisitiert eine
> Reihenfolgen Definition?

im determistischen Compiler ja
wer garantiert dir das dein Compiler deterministisch ist?

Worauf du vielleicht hinaus willst ist .. implementation defined 
behaviour

es gibt
1) undefined behaviour
2) implementation defined behaviour
3) unspecified behaviour

Die letzten beiden unterscheiden sich ... (Quelle wiki)
1
The exact definition of unspecified behavior varies. In C++, it is defined as "behavior, for a well-formed program construct and correct data, that depends on the implementation."[4] Unlike implementation-defined behavior, there is no requirement for the implementation to document its behavior

int x=1;
x/0 => undefined, kann beim gleichen Compiler jedes mal anderes Ergebnis 
herauskommen

{int x; print("%d", x);} => undefined, kann beim gleichen Compiler
jedes mal anderes Ergebnis herauskommen

bei f()+g() Aufrufreihenfolge scheint es sich um "impl. defined 
behaviour"
zu handeln

von (prx) A. K. (prx)


Lesenswert?

Martin schrieb:
> bei f()+g() Aufrufreihenfolge scheint es sich um "impl. defined
> behaviour" zu handeln

Nein. Implementation defined ist beispielsweise die Länge externer Namen 
jenseits der Mindestvorgabe des Standards. Das findest du irgendwo im 
Manual angegeben. Hier ist es jedoch unspecified, weil es je nach Laune 
des Compilers (Kontext, Optimierung) unterschiedlich sein kann.

Im Unterschied zu "undefined" ist aber sicher, dass du ein Ergebnis 
bekommst, dass sich aus der Addition der Return-Werte ergibt (ohne 
Überlauf).

von (prx) A. K. (prx)


Lesenswert?

Martin schrieb:
> im determistischen Compiler ja
> wer garantiert dir das dein Compiler deterministisch ist?

Wobei ein Determinismus sich als mathematisch chaotisch erweisen kann 
(Schmetterlingseffekt).

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.