Hallo ich habe eine Anfängerfrage: X[n++]=n; oder X[n]=n++; was macht den Unterschied?
Nix. Das Verhalten ist equivalent. Der Compiler wird daraus mit hoher Wahrscheinlichkeit sogar identischen Assembler-Code erzeugen.
Nix schrieb: > Nix. Das Verhalten ist equivalent. Der Compiler wird daraus mit > hoher Wahrscheinlichkeit sogar identischen Assembler-Code erzeugen. Sicher? Ich hätte erwartet das die rechte Seite zuerst ausgeführt würde!
Kleiner Hüpfer schrieb: > Hallo ich habe eine Anfängerfrage: > > X[n++]=n; > > oder > > X[n]=n++; > > was macht den Unterschied? Nix schrieb: > Nix. Exact! Beies ist undefined behaviour in C und C++. Das sieht man auch an den Warnungen die gcc gibt:
1 | xxx.cpp: In function ‘int main()’: |
2 | xxx.cpp:5:6: warning: operation on ‘n’ may be undefined [-Wsequence-point] |
3 | X[n++]=n; |
4 | ~^~ |
5 | xxx.cpp:6:9: warning: operation on ‘n’ may be undefined [-Wsequence-point] |
6 | X[n]=n++; |
7 | ~^~ |
Das ++ nach der Variable bedeutet: "Inkrementiere nach der Zeile/Anweisung".
Kleiner Hüpfer schrieb: > Sicher? Ich hätte erwartet das die rechte Seite zuerst ausgeführt würde! Das währe dann der Unterschied zwischen n++ und ++n
Nix schrieb: > Nix. Das ist richtig. Nix schrieb: > Der Compiler wird daraus mit hoher > Wahrscheinlichkeit sogar identischen Assembler-Code erzeugen. Das ist möglich, wäre aber Zufall. Da sich in beiden Varianten kein Sequence-Point zwischen n und n++ befindet, sind beide Varianten nicht zulässig, und resultieren in undefined behaviour. Oliver
Beides hat undefinieres Verhalten, weil Seiteneffekte (++ in diese Fall) erst nach einem Sequenzpunkt definiert sind. D.h. möglicherweise ist n erst nach der Zuweisung implementiert, möglicherweise aber auch vorher.
Paul schrieb: > das währe dann der Unterschied zwischen n++ und ++n äh nö, bei ++n wird vorher incrementiert. Bei nur++ nachher.
Mac schrieb: > Beides hat undefinieres Verhalten, weil Seiteneffekte (++ in diese > Fall) erst nach einem Sequenzpunkt definiert sind. D.h. möglicherweise > ist n erst nach der Zuweisung implementiert, möglicherweise aber auch > vorher. Also sollte man besser : x[n]=n; n++; schreiben und nicht versuchen alles in eine Zeile zu quetschen...
Nix schrieb: > Das ++ nach der Variable bedeutet: "Inkrementiere nach der > Zeile/Anweisung". Nein, es heisst: Inkrementiere irgendwann nach diesem Zugriff
Sollte man in dem Fall auf jeden Fall tun. Da die Zeile allerdings mit an Sicherheit grenzender Wahrscheinlichkeit in einer for- oder while-Schleife steht, kannst du das n++ auch dort hinein packen. Oliver
Man muss nicht jeden Unsinn machen, den man darf. Also schreib entweder X[n]=n;n++; oder n++;X[n]=n; oder X[n+1]=n;n++; je nachdem, was das Programm tun soll. Dann gibt es keine Missverständnisse mit dem Compiler.
Eigentlich sind das einfachste C-Grundlagen. ---------------- Vorher Var == ?? Vorher n == 2; Var = n++; Nachher Var == 2; Nachher n == 3; ---------------- Vorher Var == ?? Vorher n == 2; Var = ++n; Nachher Var == 3; Nachher n == 3; Die Stichworte hierzu lauten post- bzw. pre-imcrement.
Kleiner Hüpfer schrieb: > schreiben und nicht versuchen alles in eine Zeile zu quetschen... Die Zeile hat meist keine Bedeutung in C. Aber das ';'.
:
Bearbeitet durch User
Amateur schrieb: > Eigentlich sind das einfachste C-Grundlagen. Eigentlich solltest du nochmal über das Problem nachdenken ;-)
Ich wäre nie auf die Idee gekommen, solche Zeilen zu schreiben. Also habe ich es mal mit dem gcc ausprobiert.
1 | #include <stdio.h> |
2 | |
3 | void main(int argc,char **argv){ |
4 | int X[5] ={-1,-1,-1,-1,-1}; |
5 | int i; |
6 | |
7 | i=2; |
8 | X[i++]=i; |
9 | |
10 | X[i]=i++; |
11 | printf("\n%d %d %d %d %d\n",X[0],X[1],X[2],X[3],X[4]); |
12 | |
13 | } |
output war: -1 -1 3 -1 3 X[0],X[1] wurden nie angefasst X[2] = 3 ! addressiert wurde erwartungsgemäß X[2], aber drin steht eine 3, d.h. i wurde vorher inkrementiert. X[3] wurde nie angefasst X[4] = 3; (=i++; ergibt nicht inkrementierten Wert, bei der Aressierung wird der inkrementierte Wert benutzt) Hätte ich so nicht erwartet, aber ich hätte diese Zeilen auch nie geschrieben.
Martin B. schrieb: > Hätte ich so nicht erwartet, Wenn du nochmals über den Begriff "undefined behaviour" nachdenkst, sollte dir einiges klarer werden. Aus der C FAQ: "The C FAQ defines “undefined behavior” like this: Anything at all can happen; the Standard imposes no requirements. The program may fail to compile, or it may execute incorrectly (either crashing or silently generating incorrect results), or it may fortuitously do exactly what the programmer intended." Oliver
:
Bearbeitet durch User
> Also schreib entweder > > X[n]=n;n++; > > oder > > n++;X[n]=n; > > oder > > X[n+1]=n;n++; > > je nachdem, was das Programm tun soll. Exakt. Was ist eigentlich damit gewollt? - Arrayelement beschreiben mit Wert==Index? - Arrayelement beschreiben mit Wert-eins-mehr-als-Index? - Danach soll Index aufs nächste Arrayelement "zeigen"? Warum? Schleifenkörper? ächste Instruktion lautet wie?
Martin B. schrieb: > Ich wäre nie auf die Idee gekommen, solche Zeilen zu schreiben. Also > habe ich es mal mit dem gcc ausprobiert. $ gcc -O2 u.c -o u ; ./u -1 -1 3 -1 3 $ clang u.c -o u ; ./u -1 -1 2 -1 3 So sieht es aus, das undefinierte Verhalten.
Amateur schrieb: > Die Stichworte hierzu lauten post- bzw. pre-imcrement. Nein, das Stichwort lautet "Sequence Point". Siehe http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf (Abschnitt 5.1.2.3, sowie Annex C)
Luther B. schrieb: > So sieht es aus, das undefinierte Verhalten. Da macht ja jeder was er will! :-) Doof am gcc: Der warnt davor nicht in der Standarteinstellung. Das braucht -Wall o.ä.
Martin B. schrieb: > Luther B. schrieb: >> So sieht es aus, das undefinierte Verhalten. > > > Da macht ja jeder was er will! :-) > > Doof am gcc: Der warnt davor nicht in der Standarteinstellung. Das > braucht -Wall o.ä. $ gcc -O2 u.c -o u ; ./u -1 -1 3 -1 3 $ clang u.c -o u ; ./u -1 -1 2 -1 3 $ tcc u.c -o u ; ./u # https://bellard.org/tcc/ -1 -1 3 3 -1 (Visual Studio 2017): -1 -1 2 3 -1
:
Bearbeitet durch User
Dirk B. schrieb: > Hängt das Ergebnis beim gcc von der Optimierungsstufe ab? In diesem Beispiel kommt bei meinem Compiler das gleiche raus, das ist aber nicht immer so. Siehe z.B. damals, als der GCC-Optimierer die kaputten overflow checks wegoptimiert hat => https://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475
Martin B. schrieb: > Doof am gcc: Der warnt davor nicht in der Standarteinstellung. Das > braucht -Wall o.ä. Da ist clang (wie meistens) überlegen:
1 | $ clang 1.c |
2 | |
3 | 1.c:3:1: warning: return type of 'main' is not 'int' [-Wmain-return-type] |
4 | void main(int argc,char **argv){ |
5 | ^ |
6 | 1.c:3:1: note: change return type to 'int' |
7 | void main(int argc,char **argv){ |
8 | ^~~~ |
9 | int |
10 | 1.c:8:4: warning: unsequenced modification and access to 'i' [-Wunsequenced] |
11 | X[i++]=i; |
12 | ^ ~ |
13 | 1.c:10:7: warning: unsequenced modification and access to 'i' [-Wunsequenced] |
14 | X[i]=i++; |
15 | ~ ^ |
16 | 3 warnings generated. |
Tom schrieb: > Da ist clang (wie meistens) überlegen: wie genau meinst du "meistens"? und, Nebenschauplatz: wie weit ist clang für zB AVR? ich wollte das peopachten, habs aber vom Radar verloren
Luther B. schrieb: > So sieht es aus, das undefinierte Verhalten. Dafür, dass sie es sich aussuchen konnten, wie es aussehen soll, haben die aber ziemlich bescheuert gewählt. Naja. Jedem das seine...
Heiko L. schrieb: >> So sieht es aus, das undefinierte Verhalten. > > Dafür, dass sie es sich aussuchen konnten, wie es aussehen soll, haben > die aber ziemlich bescheuert gewählt. Naja. Jedem das seine... Äh, vermutlich ist Dir dann nicht ganz klar, was undefiniertes Verhalten (UB) für "die" bedeutet: "Das ist so abstrus, dass macht niemand niemals! und wenn doch, dann sitzt da ein Volltrottel, dann dürfen alle Dämme brechen, dann tragen wir keine Verantwortung mehr, dann können wir auch später noch wahllos Bits in irgendwelchen Registern kippen, dann ist der Blödmann selber schuld". UB heisst *NICHT*: "Naja, dies wäre richtig oder das, oder jenes wäre OK, also machen wir das so!" Das wäre "Unspecified Behaviour"
Oh, richtig. Müssen, sollen, können - alles kein Unterschied, righty right? Dürfen gäbe es da auch noch. Natürlich KANN man auch "undefined behaviour" im Sinne des Standards in sich konsistent umsetzen. Man MUSS nur nicht, weil der Standard (ließ: das absolute Minimum des gerade noch erträglichen) da nicht sagt, dass es "implementation defined" ist, wo dann also eine konsistente Definition vorliegen MÜSSTE. Da DARF der Compilerbauer dann also auch die Festplatte und die GIT-Repos löschen (laut C-Standard jedenfalls). Gutes Beispiel ist der int-overflow. 0x7fffffff + 1 kann 0x80000000 ergeben. Kann aber aber auch die Festplatte löschen. Alles eine Frage des Geschmacks.
:
Bearbeitet durch User
Heiko L. schrieb: > Natürlich KANN man auch "undefined behaviour" im Sinne des Standards in > sich konsistent umsetzen. Wie denn? Welches Verhalten soll denn der arme Compilerbauer umsetzen wenn nichts definiert ist?
Le X. schrieb: > Welches Verhalten soll denn der arme Compilerbauer umsetzen wenn nichts > definiert ist? Seine eigene Definition. Bei int overflow koennte man wrapping definieren, bei dem Sequencepoint Problen koennte man Auswertung von links nach rechts definieren. Das macht es aber sehr schwer schnelle Optimierer zu schreiben, d.h. es wird Geschwindigjeitseinbussen geben.
Dumdi D. schrieb: > Seine eigene Definition. Das machen doch auch alle. Denn jeder Compiler bricht ja bei der Üebrsetzung der o.a. Zeilen nicht ab, sondern generiert Code. Also gibt es intern schon irgendwelche compilerspezifoschen Definitionen, wie sowas übersetzt wird. Du bekommst nur nicht gesagt, was für welche... Oliver
:
Bearbeitet durch User
Dumdi D. schrieb: > Das macht es aber sehr schwer schnelle Optimierer zu schreiben, d.h. es > wird Geschwindigjeitseinbussen geben. Wenn man bedenkt, dass man, um garantieren zu können, dass keine Festplatten gelöscht würden, vor jeder einzelnen int-Rechenoperation prüfen müsste, dass sie keinen Overflow generiert, wäre ich mir da nicht sicher. undefined behaviour: Oh, mein Gott!! Das Ding könnte eine von zwei unabhängigen Firmen verifizierte Prüf- und Schutzroutine außer Kraft und schwere Maschinerie trotz gedrücktem Not-Aus-Schalter in Gang setzen, wenn einmal ein a*b überläuft. "Aha, c=a*b läuft über. Das merke ich mir mal und - oh: hier diese if-Abfrage benutzt c, da setzte ich als Ergebnis mal - hmm, wie ist es günstiger - "true" ein. Und hier, diese Abfrage.. Oh oh, "c+10". Das macht die Sache jetzt aber nicht besser. Na gut, sagen wir mal, ich habe gerade eben INT_MAX für c eingesetzt, was das "true" als Ergebnis zulässt und habe jetzt: Tada - Noch einen Overflow! In diesem Fall darf ich jetzt nämlich wieder "true" einsetzen und den nachfolgenden Jump ausführen, was viel günstiger ist, als dieses "void emergency_shutdown()". Jaja, ein einzelner Overflow hätte da nicht gereicht. Musste mir Mühe schon geben..."
Oliver S. schrieb: > Dumdi D. schrieb: >> Seine eigene Definition. > > Das machen doch auch alle. Denn jeder Compiler bricht ja bei der > Üebrsetzung der o.a. Zeilen nicht ab, sondern generiert Code. Also gibt > es intern schon irgendwelche compilerspezifoschen Definitionen, wie > sowas übersetzt wird. Du bekommst nur nicht gesagt, was für welche... > > Oliver Unterstelle einem Verhalten nicht eine Definition. Es kann reiner Zufall sein, was bei der Übersetzung von UB hearuskommt, z.B. kann durch das zusammenwirken unabhängiger Optimierungsstufen die Reihenfolge der Inkrementierungen innerhalb eines Sequence Points geändert werden. Der Compiler muss nur über eine Defintion verfügen, welche gültige Reihenfolgen erzeugt, ohne das im Einzelnen klar wäre, welche das in einem konkreten Fall ist. Der Compiler könnte also z.B. für eine andere CPU eine andere Reihenfolge erzeugen, weil der Optimierer dort anders arbeitet. gcc hat z.B. Optimierungen die auf Heuristiken beruhen (-fguess-branch-probability) und bei profile-guided Optimierung ist das Ergebnis im Grunde zufällig, wenn es von nicht vollständig reproduzierbaren Profileruns abhängt. Und UB kann deine Festplatte löschen: https://kristerw.blogspot.de/2017/09/why-undefined-behavior-may-call-never.html
Luther B. schrieb: > Und UB kann deine Festplatte löschen: > > https://kristerw.blogspot.de/2017/09/why-undefined-behavior-may-call-never.html Dazu muss man die Bombe (system("rm -rf /");) aber schon selbst einbauen. Der Compiler macht das nicht.
Dann nenne die Funktion meinetwegen NotAus(), bei der die Maschine destruktiv runtergefahren wird, um z.B. Menschen zu schützen...
Wie man die Bombe nennt, ist egal. Wer hochgefährliche Sachen schreibt, kann die Verantwortung nicht auf den Compiler oder die Programmiersprache schieben.
:
Bearbeitet durch User
Jobst Q. schrieb: > X[n]=n;n++; > > oder > > n++;X[n]=n; > > oder > > X[n+1]=n;n++; Diesen Unfug auf Teufel komm raus Zeilen sparen zu wollen haben wir zuletzt auf einem Commodore CBM 4008 gemacht. Da war das Problem, daß der Basic Source Code sonst wegen der Zeilenumbrüche zu groß wurde um ins Ram zu passen. Ich denke doch daß man in heutiger Zeit mit Terabytes an Plattenplatz und Gigabytes an RAM die paar carriage return / Line Feed spendieren kann um das Progamm eindeutig und lesbar zu machen.
Luther B. schrieb: > Unterstelle einem Verhalten nicht eine Definition. Es kann reiner Zufall > sein, was bei der Übersetzung von UB hearuskommt, Genauso war das auch gemeint. Es ist reiner Zufall, was da raus kommt, aber es kommt was raus. Oliver
Jobst Q. schrieb: > Luther B. schrieb: >> Und UB kann deine Festplatte löschen: >> >> https://kristerw.blogspot.de/2017/09/why-undefined-behavior-may-call-never.html > > Dazu muss man die Bombe (system("rm -rf /");) aber schon selbst > einbauen. Der Compiler macht das nicht. Es reicht aus, wenn system einen buffer benutzt, der vom User befüllt werden kann, UB einen check löscht, was dazu führt, dass system mit dem buffer aufgerufen wird. Zahlreiche CVE basieren z.B. auf UB, bei denen checks ausser Kraft gesetzt wurde. Hier z.B. sehr ein lehrreiches Beispiel: https://lwn.net/Articles/342330/ Bei UB verwandelt sich potentiell dein ganzer Code in eine Bombe.
Heiko L. schrieb: > Gutes Beispiel ist der int-overflow. > 0x7fffffff + 1 kann 0x80000000 ergeben. Kann aber aber auch die > Festplatte löschen. Alles eine Frage des Geschmacks. Heiko L. schrieb: > Das Ding könnte eine von zwei > unabhängigen Firmen verifizierte Prüf- und Schutzroutine außer Kraft und > schwere Maschinerie trotz gedrücktem Not-Aus-Schalter in Gang setzen, > wenn einmal ein a*b überläuft. Halt mal lieber ruhig. Diese blumigen Metaphern wie "Festplatte löschen" oder "Dämonen, die aus der Nase fliegen" sind Übertreibungen und dienen lediglich dazu, einem Anfänger die Bedeutung von "undefined Behaviour" anschaulich nahezubringen. Dass es in der Praxis keine Implementierung eines C-Compilers gibt der dir bei einem signed-overflow die Festplatte löscht weißt du genauso gut wie ich. Keinen Grund, sich deswegen aufzuführen wie ein Gartenschlauch.
C99RationaleV5.10.pdf sagt auf Seite 11: The terms unspecified behavior, undefined behavior, and implementation-defined behavior are used to categorize the result of writing programs whose properties the Standard does not, or cannot, completely describe. The goal of adopting this categorization is to allow a certain variety among implementations which permits quality of implementation to be an active force in the marketplace as well as to allow certain popular extensions, without removing the cachet of conformance to the Standard. Informative Annex J of the Standard catalogs those behaviors which fall into one of these three categories. Unspecified behavior gives the implementor some latitude in translating programs. This latitude does not extend as far as failing to translate the program, however, because all possible behaviors are “correct” in the sense that they don’t cause undefined behavior in any implementation. Undefined behavior gives the implementor license not to catch certain program errors that are difficult to diagnose. It also identifies areas of possible conforming language extension: the implementor may augment the language by providing a definition of the officially undefined behavior. Implementation-defined behavior gives an implementor the freedom to choose the appropriate approach, but requires that this choice be explained to the user. Behaviors designated as implementation-defined are generally those in which a user could make meaningful coding decisions based on the implementation’s definition. Implementors should bear in mind this criterion when deciding how extensive an implementation definition ought to be. As with unspecified behavior, simply failing to translate the source containing the implementation-defined behavior is not an adequate response.
Da finde ich den Standard selber klarer formuliert: undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements NOTE 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). unspecified behavior behavior where this International Standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance EXAMPLE An example of unspecified behavior is the order in which the arguments to a function are evaluated. Oliver
Luther B. schrieb: > Es reicht aus, wenn system einen buffer benutzt, der vom User befüllt > werden kann, UB einen check löscht, was dazu führt, dass system mit dem > buffer aufgerufen wird. Zahlreiche CVE basieren z.B. auf UB, bei denen > checks ausser Kraft gesetzt wurde. Hier z.B. sehr ein lehrreiches > Beispiel: > > https://lwn.net/Articles/342330/ > > Bei UB verwandelt sich potentiell dein ganzer Code in eine Bombe. Mein Code ist das nicht. Und auch der Beispielcode verwandelt sich nicht in eine Bombe, sondern schafft einen Zugang u.a für Bombemleger. Ist auch nicht schön, aber doch ein feiner Unterschied.
Der Andere schrieb: >> X[n+1]=n;n++; > > Diesen Unfug auf Teufel komm raus Zeilen sparen zu wollen haben wir > zuletzt auf einem Commodore CBM 4008 gemacht. Da war das Problem, daß > der Basic Source Code sonst wegen der Zeilenumbrüche zu groß wurde um > ins Ram zu passen. > > Ich denke doch daß man in heutiger Zeit mit Terabytes an Plattenplatz > und Gigabytes an RAM die paar carriage return / Line Feed spendieren > kann um das Progamm eindeutig und lesbar zu machen. Die Zeile ist sowohl eindeutig als auch lesbar. Anweisungen werden in C mit ';' getrennt. Die grafische Verteilung mit Zeilenumbrüchen unterliegt der künstlerischen Freiheit des Programmierers. RAM und Plattenplatz ist unwesentlich gegenüber dem Platz auf dem Bildschirm, den man noch überschauen kann. Wenn er vertikal ausufert ist das genauso ein Hindernis für die Lesbarkeit wie ein horizontales Ausufern. Davon kann aber bei 13 Zeichen in der Zeile noch nicht die Redes sein.
Jobst Q. schrieb: > Und auch der Beispielcode verwandelt sich nicht in eine Bombe, sondern > schafft einen Zugang u.a für Bombemleger. Ist auch nicht schön, aber > doch ein feiner Unterschied Das ist eher noch schlimmer, da jemand mit Absichten dann das System uebernimmt. Bei Zufall hat man hingegen ja immer eine Chance.
Le X. schrieb: > Dass es in der Praxis keine Implementierung eines C-Compilers gibt der > dir bei einem signed-overflow die Festplatte löscht weißt du genauso gut > wie ich. Genügt doch, wenn in dem Fall ein paar CPU-Flags nicht gecleared werden und deshalb der nächste Jump ein bisschen daneben geht. Ein Compiler-Bug wäre das nur aus Kulanz. Ein brauchbarer Compiler ganz sicher nicht. Ich kann's auch wiederholen: Der Standard definiert in der Hinsicht nur das absolute Minimum dessen, was erforderlich ist, um überhaupt ein lauffähiges Programm schreiben zu können. Wenn eines so sicher ist, wie das Amen in der Kirche, dann dass solche ungeprüften int-Additionen in der Praxis durchaus vorkommen. In Java zB ist das anders. Da kann man an den aufgerufenen Funktionen sehen, ob da potentiell I/O stattfindet.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.