Ohne Optimierung ist alles ok (es gibt keinen SegFault), aber mit
Optimierung gibt es bei r = new int() einen SegFault, wenn man das ganze
ausfuehrt.
Liegt das daran, das r wegoptimiert wurde, und der return-Wert von new
in einem nicht-allokierten Bereich des Stacks landet? Oder ueberschreibt
der return-Wert von new die Ruecksprungadresse (weil kein Platz auf dem
Stack allokiert wurde, da r wegoptimiert wurde)?
Gruesse
Dr. Sommer schrieb:> Kaj G. schrieb:>> Vereinfachtes Beispiel:>> Und du bist ganz sicher dass exakt genau dieser Code eine SegFault> produziert?
Bei mir gibts mit gcc (version 8.2.1 20181127) mit -O1 (oder 2/3/s) auch
nen Segmentation fault.
Bei mir läufts ohne segfault (gcc 7.3.0, 64bit). Allerdings darf der
Compiler bei "undefined behaviour" alles machen, gemeinerweise tut er
das auch hin und wieder und "optimiert" ganze Programmteile weg.
Auskommentierte return-Zeile aktivieren und der Segfault ist weg.
Die Frage ist, wieso das "return r" auskommentiert wurde ...
Compiler halten sich bei hohen Optimierungsstufen nicht an die
Zeilennummern des Source-Codes...^^
Mysteriös. Ich wüsste jetzt gerade nicht dass das weglassen eines return
wirklich UB ist. Der Pointer wird ja nie dereferenziert. Ich guck mir
das gleich mal genauer an...
Dr. Sommer schrieb:> Ich wüsste jetzt gerade nicht dass das weglassen eines return> wirklich UB ist. Der Pointer wird ja nie dereferenziert. Ich guck mir> das gleich mal genauer an...
Ich hab den Standard gerade auch nicht da, aber nicht der Pointer ist
das Problem, sondern dass eine Funktion ohne return undefined ist.
Es liegt vermutlich daran, dass ein externer Aufrufer in der Deklaration
der Funktion sieht, dass ein int* zurückgegeben wird. Je nach
Callingconvention erwartet er diesen auf dem Stack oder in einem
Register.
Der Code der die Funktion definiert sagt aber nicht, welchen Wert der
Returnwert haben soll. Und weil es keine allgemeine gültige Lösung für
diesen Fall gibt, ist es vermutlich undefined behaviour.
Mampf F. schrieb:> Die Frage ist, wieso das "return r" auskommentiert wurde ...
Siehe:
> die Frage geht auf diesen Fehler zurueck:> Beitrag "Re: AVR Simulator mit grafischer Benutzeroberfläche für Linux"
Dort sorgte der vergessene Rueckgabewert fuer eben diesen SegFault (ist
ja schon gefixt).
Ich wollte nur besser verstehen, warum es genau zum SegFault kommt. :)
Dr. Sommer schrieb:> Mysteriös. Ich wüsste jetzt gerade nicht dass das weglassen eines return> wirklich UB ist.
Nur bei den wenigsten Compilern und bei gcc erst ca. ab Version 8.
Heiko L. schrieb:> Das return hätte hier wohl echt "ret" geheißen.
Ein "return" definiert, dass es an dieser Stelle nicht weiter geht, und
mit Wert ergibt sich der Return-Wert. Ansonsten ist das u.U. einfach nur
ein Sprung ans Funktionsende. Immerhin sollte klar sein, dass jede
non-inline Funktion mit "ret" beendet wird, ob mit Wert oder nicht.
Das "ret" ganz wegzulassen, aber den Rest drin zu lassen, wirkt eher
ungewöhnlich.
A. K. schrieb:> Ein "return" definiert, dass es an dieser Stelle nicht weiter geht, und> mit Wert ergibt sich der Return-Wert. Ansonsten ist das u.U. einfach nur> ein Sprung ans Funktionsende. Immerhin sollte klar sein, dass jede> non-inline Funktion mit "ret" aufhört, ob mit Wert oder nicht.>> Das "ret" ganz wegzulassen, ist eher ungewöhnlich.
Das denken anscheinend auch die meisten Compiler-Programmierer außer
denen von gcc. :)
Heiko L. schrieb:> Nur bei den wenigsten Compilern und bei gcc erst ca. ab Version 8.
Undefined behaviour wird ja nicht vom Compiler, sondern vom Standard
festgelegt.
Der Compiler kann dann daraus etwas mehr oder weniger sinnvolles machen,
aber es darf jeder Compiler eben so machen wie es für diesen am besten
ist oder auch einfach am leichtesten zu implementieren.
Heiko L. schrieb:>> Mysteriös. Ich wüsste jetzt gerade nicht dass das weglassen>> eines return wirklich UB ist.> Nur bei den wenigsten Compilern und bei gcc erst ca. ab Version 8.
Das hat mit dem Compiler nichts zu tun, sondern mit der Sprache.
Was ist denn der genaue Rückgabewert einer Funktion, die kein "return"
enthält?
S. R. schrieb:> Was ist denn der genaue Rückgabewert einer Funktion, die kein "return"> enthält?
Ja wie ist das z.B. in C im Falle einer impliziten Deklaration, die als
"int" angenommen wird? Undefiniert?
Bei x86-64 GCC Version 7.3.0 funktionierts, bei Clang 6.0.0 auch. Beim
ARM-GCC 8.2.1 hingegen nicht - auch hier wird das "pop {r4, pc}"
weggelassen.
Im C++-Standard Draft (n4659) heißt es in 9.6.3 2:
"Flowing off the end of a constructor, a destructor, or a function with
a cv void return type is equivalent to a return with no operand.
Otherwise, flowing off the end of a function other than main (6.6.1)
results in undefined behavior."
Also doch UB! Dann ist der Compiler nicht schuld. Es wird also kein
Rücksprung ausgeführt - nachdem der "operator new" zurück kehrt, geht
der Programmablauf ins Nirvana.
S. R. schrieb:> Was ist denn der genaue Rückgabewert einer Funktion, die kein "return"> enthält?
Es hätte etwas zufälliges sein können, ähnlich einer lokalen
nicht-initialisierten Variable. Aber weil es eben UB ist, passiert
"irgendwas".
Heiko L. schrieb:> Ob es das wirklich ist aber nicht.
Da hast du recht, aber damit ist es dann ja nicht mehr C++11 Code,
sondern z.B. C++11 mit GCC 7.1, weil deine Compilerversion das Verhalten
festlegt. Und eigentlich müssten auch noch die Compilerparameter dazu,
falls sich undefined behaviour je nach Optimizer anders verhält.
Für Bibliotheken, die auf verschiedenen Compilern gebaut werden ist das
natürlich tötlich.
M.K. B. schrieb:>> Ob es das wirklich ist aber nicht.> Da hast du recht, aber damit ist es dann ja nicht mehr C++11 Code,> sondern z.B. C++11 mit GCC 7.1, weil deine Compilerversion das Verhalten> festlegt.
Oder das Verhalten von allen Compilern außer gcc > 8. Das wäre dann
Betrachtungssache. "Legal" ist es jedenfalls.
M.K. B. schrieb:> Für Bibliotheken, die auf verschiedenen Compilern gebaut werden ist das> natürlich tötlich.Dafür wäre es tötlich sich nur am C(++)-Standard zu orientieren.
"Libraries? Was das? ABI???"
Heiko L. schrieb:> Das denken anscheinend auch die meisten Compiler-Programmierer außer> denen von gcc. :)
Die beim gcc sind in der hinsicht noch konservativ. clang++ sieht ub und
optimiert alles weg.
Ich hab dem Orginalbeispiel zwei Zeilen hinzugefügt (die baas), damit er
ohne ub nicht alles wegoptimieren darf:
https://godbolt.org/z/3eFFKd
Das Verhalten mag "legal" sein, sinnvoll ist es nicht - es bereitet dem
Programmierer nur Probleme. Entweder gibt das ein "error" statt nur ein
"warning", oder er macht irgendwas sinnvolles (ret, Programmabbruch,
etc). Einfach (überraschend) kaputten Kode zu produzieren, hilft
keinem.
Und jetzt sollen die Legal Weasels nicht wieder ankommen und das
verteidigen: Die Qualität eines Compiler zeigt sich auch dadurch, den
Programmierer bei Undefined Behaviour zu unterstützen und nicht ihn zu
verarschen.
A. K. schrieb:> Das "ret" ganz wegzulassen, aber den Rest drin zu lassen, wirkt eher> ungewöhnlich.
Ich spekuliere mal warum der gcc das macht. Der Compiler sieht in foo
den Aufruf einer unbekannten Funktion "operator new(unsigned long)".
Nach dem Aufruf fehlt am Ende der Funktion ein return. Wenn irgendwann
im Programablauf diese Stelle erreicht wird, ist das ub und es ist egal
was passiert (nachher und vorher). Damit kann der Compiler davon
ausgehen, dass diese Stelle nie erreicht wird. Es wird also kein ret
benötigt.
Beim clang sieht das anders aus. Clang kann in vielen Fällen new bzw
malloc wegoptimieren. Man kann also annehmen, dass er den Inhalt von
operator new kennt. Damit sieht er, dass operator new verlassen wird und
ub zuschlägt.
Die richtig interessante Frage ist: Ist es ub, wenn operator new ne
Exception wirft?
mh schrieb:> Die richtig interessante Frage ist: Ist es ub, wenn operator new ne> Exception wirft?
Nein, das ist sogar ganz normal! std::bad_alloc wird geworfen wenn nicht
genug Speicher da ist. Das ist sehr sinnvoll, denn dann spart man sich
das ständige if(!mem) abort() wie in C.
foobar schrieb:> Entweder gibt das ein "error" statt nur ein> "warning"
Man sollte sowieso immer mit -Werror kompilieren.
Der Compiler darf halt annehmen dass UB niemals auftritt. Daher geht er
davon aus, dass "new" nie zurückkehrt, ähnlich wie abort() - deswegen
wird der Compiler nach einen abort()-Aufruf auch keinen Aufräum Code ala
"ret" oder "pop" einbauen.
foobar schrieb:> Das Verhalten mag "legal" sein, sinnvoll ist es nicht - es bereitet dem> Programmierer nur Probleme. Entweder gibt das ein "error" statt nur ein> "warning", oder er macht irgendwas sinnvolles (ret, Programmabbruch,> etc). Einfach (überraschend) kaputten Kode zu produzieren, hilft> keinem.>> Und jetzt sollen die Legal Weasels nicht wieder ankommen und das> verteidigen: Die Qualität eines Compiler zeigt sich auch dadurch, den> Programmierer bei Undefined Behaviour zu unterstützen und nicht ihn zu> verarschen.
Ich bin mal Logic Weasel (nicht Legal Weasel). Es ist nicht zwingen ein
Fehler. Es ist ein Fehler, wenn der Punkt im Program erreicht wird. Der
Compiler darf also keinen Fehler anzeigen. Dass es eine Warnung gibt,
ohne irgendwelche Warnungen explizit aktiviert zu haben, ist schon
ungewöhnlich und für einige sicher ein Grund dem Compiler minderwertige
Qualität vorzuwerfen.
Dr. Sommer schrieb:> mh schrieb:>> Die richtig interessante Frage ist: Ist es ub, wenn operator new ne>> Exception wirft?>> Nein, das ist sogar ganz normal! std::bad_alloc wird geworfen wenn nicht> genug Speicher da ist. Das ist sehr sinnvoll, denn dann spart man sich> das ständige if(!mem) abort() wie in C.
Hmmm. Ok, die noch viel interessantere Frage ist: Ist obiges Beispiel
ub, wenn operator new ne Exception wirft?
;-)
foobar schrieb:> Und jetzt sollen die Legal Weasels nicht wieder ankommen und das> verteidigen: Die Qualität eines Compiler zeigt sich auch dadurch, den> Programmierer bei Undefined Behaviour zu unterstützen und nicht ihn zu> verarschen.
Naja, da du dort eine Warnung kriegst, ist eigentlich alles i.O.
Für den konkreten Fall könnte fordern, dass Warnings-as-Errors Pflicht
wird.
"Garbage in - garbage out" finde ich z.T. aber auch ziemlich fragwürdig,
wenn der "Garbage" explizit so geschrieben wurde. Wenn das Programm
keinen Sinn machen kann, macht es auch keinen Sinn, es zu übersetzen.
> Der Compiler darf halt annehmen dass UB niemals auftritt. Daher geht er> davon aus, dass "new" nie zurückkehrt, ähnlich wie abort() - deswegen> wird der Compiler nach einen abort()-Aufruf auch keinen Aufräum Code ala> "ret" oder "pop" einbauen.
Wer ein noch nie ein Programm mit UB geschreiben hat soll den ersten
Stein werfen ;-) Mit anderen Worten: die Annahme, dass UB niemals
auftritt ist einfach falsch!
Und für so etwas wie abort gibt es das noreturn-Attribut ...
foobar schrieb:> Mit anderen Worten: die Annahme, dass UB niemals> auftritt ist einfach falsch!
Compiler müssen dies aber annehmen, weil sich C und C++ sonst kaum
sinnvoll/effizient übersetzen lassen. Der Compiler muss bei jeder
schnöden Pointer-Dereferenzierung annehmen, dass der Pointer gültig ist
und hier kein UB eintritt; würde er das nicht annehmen, müsste bei jedem
Pointer eine Gültigkeits-Prüfung erfolgen. Das wäre ziemlich ineffizient
und sogar fast unmöglich, da ja nicht nur 0-Pointer ungültig sind.
foobar schrieb:> Und für so etwas wie abort gibt es das noreturn-Attribut ...
Und was ist, wenn die Funktion je nach Umständen mal zurückkehrt und mal
nicht? Das ist alles nicht so einfach ...
Dr. Sommer schrieb:> foobar schrieb:>> Mit anderen Worten: die Annahme, dass UB niemals>> auftritt ist einfach falsch!>> Compiler müssen dies aber annehmen, weil sich C und C++ sonst kaum> sinnvoll/effizient übersetzen lassen. Der Compiler muss bei jeder> schnöden Pointer-Dereferenzierung annehmen, dass der Pointer gültig ist> und hier kein UB eintritt; würde er das nicht annehmen, müsste bei jedem> Pointer eine Gültigkeits-Prüfung erfolgen. Das wäre ziemlich ineffizient> und sogar fast unmöglich, da ja nicht nur 0-Pointer ungültig sind.
Das stimmt zwar allgemein, aber im Beispiel
1
int *x = nullptr;
2
*x = 1;
wissen viele Compiler, dass das Unsinn ist. Der Standard fordert hier
aber keinen Abbruch, weil offensichtlich Unfug, sondern z.T. denken die
sich dann "och, dann mache ich da mal irgendetwas."
Das wäre dann nachvollziehbar, wenn der Compiler nicht wüsste, dass *x
undefined ist.
https://godbolt.org/z/Qhi53z
Generiert eine Runtime-Exception. Das ist imho eine wirklich fragwürdige
Entscheidung.
Heiko L. schrieb:> Das stimmt zwar allgemein, aber im Beispielint *x = nullptr;> *x = 1;> wissen viele Compiler, dass das Unsinn ist. Der Standard fordert hier> aber keinen Abbruch, weil offensichtlich Unfug, sondern z.T. denken die> sich dann "och, dann mache ich da mal irgendetwas."
Warum gehst du davon aus, dass da Idioten oder Menschen mit bösen
Absichten arbeiten?
> Das wäre dann nachvollziehbar, wenn der Compiler nicht wüsste, dass *x> undefined ist.> https://godbolt.org/z/Qhi53z> Generiert eine Runtime-Exception. Das ist imho eine wirklich fragwürdige> Entscheidung.
Hast du mit der Maus mal im CompilerExplorer über das von 2 Compilern
generierte ud2 gehovert? Lies den Tooltip und denk nochmal drüber nach,
warum sie das machen und ob es Leute gibt die sich beschweren, wenn das
geändert wird.
mh schrieb:> Warum gehst du davon aus, dass da Idioten oder Menschen mit bösen> Absichten arbeiten?
Das tue ich nicht. Man merkt aber doch, dass es eine gewisse
Vernachlässigung von nicht-normativen Richtlinien gibt. Ökonomie...
mh schrieb:> Hast du mit der Maus mal im CompilerExplorer über das von 2 Compilern> generierte ud2 gehovert? Lies den Tooltip und denk nochmal drüber nach,> warum sie das machen und ob es Leute gibt die sich beschweren, wenn das> geändert wird.
Och, das habe ich schon ein wenig. Es gibt Fälle, wo die Entscheidung
nicht ohne Weiteres möglich ist. Deswegen könnte so eine Forderung auch
niemals normativ für den Standard sein. Ebenso wie die Fehlermeldungen.
Es ist halt einfach eine Frage, wie viel Aufwand man in solche Sachen
stecken will. Ein "-fsane-source" Flag wäre imho jedenfalls interessant.
Heiko L. schrieb:> Das stimmt zwar allgemein, aber im Beispiel> int *x = nullptr;> *x = 1;> wissen viele Compiler, dass das Unsinn ist.
Du verlangst also, dass jeder standardkonforme Compiler mindestens
die Analysefähigkeiten des besten Compilers beherrschen muss?
Was ist, wenn zwischen den beiden Zeilen noch 400 MB an Code rumstehen,
die möglicherweise mit *x arbeiten? Soll jeder Compiler beliebig
komplexe Probleme lösen können?
Heiko L. schrieb:> Ein "-fsane-source" Flag wäre imho jedenfalls interessant.
Bei genauerer Überlegung wäre ein "-fgarbage" noch wesentlich
interessanter, um solche Fälle explizit zuzulassen.
S. R. schrieb:> Du verlangst also, dass jeder standardkonforme Compiler mindestens> die Analysefähigkeiten des besten Compilers beherrschen muss?
Das wäre grotesk. Lies noch mal ein wenig genauer.
Heiko L. schrieb:>> Du verlangst also, dass jeder standardkonforme Compiler mindestens>> die Analysefähigkeiten des besten Compilers beherrschen muss?>> Das wäre grotesk. Lies noch mal ein wenig genauer.
Nun, man könnte im Standard festschreiben, dass jede Form von UB sofort
zu einem abort() führen muss. Dann kann man aber nicht mehr optimieren.
Man könnte im Standard auch festschreiben, dass jede vom Compiler
erkannte Form von UB sofort zu einem abort() führen muss. Das ist
wertlos, weil daraus direkt folgt, dass ein UB ohne abort() schlicht vom
Compiler nicht erkannt wurde, was aber wiederum standardkonform ist.
Was wolltest du denn haben?
Oder anders gefragt: Wieviel Performanceverlust (Compilezeit/Laufzeit)
ist dir das wert?
S. R. schrieb:> Heiko L. schrieb:>>> Du verlangst also, dass jeder standardkonforme Compiler mindestens>>> die Analysefähigkeiten des besten Compilers beherrschen muss?>>>> Das wäre grotesk. Lies noch mal ein wenig genauer.>> Nun, man könnte im Standard festschreiben, dass jede Form von UB sofort> zu einem abort() führen muss. Dann kann man aber nicht mehr optimieren.>> Man könnte im Standard auch festschreiben, dass jede vom Compiler> erkannte Form von UB sofort zu einem abort() führen muss. Das ist> wertlos, weil daraus direkt folgt, dass ein UB ohne abort() schlicht vom> Compiler nicht erkannt wurde, was aber wiederum standardkonform ist.>> Was wolltest du denn haben?>> Oder anders gefragt: Wieviel Performanceverlust (Compilezeit/Laufzeit)> ist dir das wert?Heiko L. schrieb:> Es gibt Fälle, wo die Entscheidung> nicht ohne Weiteres möglich ist. Deswegen könnte so eine Forderung auch> niemals normativ für den Standard sein.Heiko L. schrieb:> Man merkt aber doch, dass es eine gewisse> Vernachlässigung von nicht-normativen Richtlinien gibt.Heiko L. schrieb:> Bei genauerer Überlegung wäre ein "-fgarbage" noch wesentlich> interessanter, um solche Fälle explizit zuzulassen.
Heiko L. schrieb:> S. R. schrieb:>> Was ist denn der genaue Rückgabewert einer Funktion, die kein "return">> enthält?>> Ja wie ist das z.B. in C im Falle einer impliziten Deklaration, die als> "int" angenommen wird? Undefiniert?
Ja, außer bei main(). Da wird automatisch 0 zurückgegeben. Ob das int
implizit oder explizit ist, ändert nichts daran.
M.K. B. schrieb:> Heiko L. schrieb:>> Ob es das wirklich ist aber nicht.>> Da hast du recht, aber damit ist es dann ja nicht mehr C++11 Code,> sondern z.B. C++11 mit GCC 7.1, weil deine Compilerversion das Verhalten> festlegt. Und eigentlich müssten auch noch die Compilerparameter dazu,> falls sich undefined behaviour je nach Optimizer anders verhält.
UB verhält sich undefiniert. Darf also von allem abhängen, auch von der
Mondphase. Es muss auch beim selben Compiler mit den selben
Einstellungen nicht zweimal das selbe passieren.
> Für Bibliotheken, die auf verschiedenen Compilern gebaut werden ist das> natürlich tötlich.
Das ist fehlerhafter Programmcode meistens.
mh schrieb:> Hmmm. Ok, die noch viel interessantere Frage ist: Ist obiges Beispiel> ub, wenn operator new ne Exception wirft?> ;-)
Nein, denn nicht das Fehlen des return-Statements löst UB aus, sondern
das Erreichen des Endes der Funktion, ohne dass da ein return-Statement
stehen würde. Bei einer Exception wird das Ende nicht erreicht, also
kein UB.
foobar schrieb:> Das Verhalten mag "legal" sein, sinnvoll ist es nicht - es bereitet dem> Programmierer nur Probleme. Entweder gibt das ein "error" statt nur ein> "warning", oder er macht irgendwas sinnvolles (ret, Programmabbruch,> etc). Einfach (überraschend) kaputten Kode zu produzieren, hilft> keinem.
Wer Warnungen ignoriert, ist selber schuld.
> Und jetzt sollen die Legal Weasels nicht wieder ankommen und das> verteidigen: Die Qualität eines Compiler zeigt sich auch dadurch, den> Programmierer bei Undefined Behaviour zu unterstützen und nicht ihn zu> verarschen.
Ja, es ist in gewissem Maße das Thema "Quality of Implementation". Man
sollte aber auch nicht vergessen, dass es UB im Standard eigentlich nur
dafür gibt, dem Compiler-Bauer das Leben zu erleichtern. Undefiniertes
Verhalten bedeutet, dass der Compiler sich nicht drum kümmern muss, was
in diesem Fall passiert. Und die Frage ist immer, wie aufwändig (wenn
überhaupt möglich) es für den Compiler ist, 100%ig eindeutig
festzustellen, dass UB vorliegt, und das ist meistens gar nicht so
leicht, wie man denkt, bzw. mit sehr viel Code-Analyse verbunden. Aber
nur dann darf er wirklich mit einem Fehler den Compile-Vorgang
abbrechen.
Wenn z.B. in diesem Programm new immer eine Exception wirft, gibt es
kein UB. Ja, weit hergeholt, aber muss dennoch berücksichtigt werden.
Heiko L. schrieb:> Das stimmt zwar allgemein, aber im Beispiel> int *x = nullptr;> *x = 1;> wissen viele Compiler, dass das Unsinn ist. Der Standard fordert hier> aber keinen Abbruch, weil offensichtlich Unfug, sondern z.T. denken die> sich dann "och, dann mache ich da mal irgendetwas."> Das wäre dann nachvollziehbar, wenn der Compiler nicht wüsste, dass *x> undefined ist.
Das sieht für dich einfach aus, ist für den Compiler aber ein
Spezialfall. Wenn da stattdessen steht:
1
int*x=myfunc();
2
*x=1;
und der Compiler nicht den Inhalt von myfunc() sieht, wird es vom
Trivialfall zu einem Fall, in dem es unmöglich ist, das festzustellen.
Man müsste also quasi jeden dieser Sonderfälle, in denen es leicht
feststellbar ist, einzeln ausprogrammieren. Das halte ich für einen
enormen Aufwand.
Heiko L. schrieb:> Es ist halt einfach eine Frage, wie viel Aufwand man in solche Sachen> stecken will.
Ja, eben. Ich vermute, du unterschätzt diesen Aufwand.
M.K. B. schrieb:> Dies ist ein interessantes Video zum Thema undefined behaviour.> Speziell zu dem Thema kein return ab 29:15.>> Youtube-Video "CppCon 2017: Piotr Padlewski “Undefined Behaviour is> awesome!”"
Danke, das ist echt interessant. :)
An diesem UB (also das vom Anfang) kann man m.M.n. sehr schoen erkennen,
warum UB so gefaehrlich ist:
GCC ohne Optimierung: kein SegFault
GCC mit Optimierung: SegFault
Clang ohne Optimierung: SIGILL
Clang mit Optimierung: kein SIGILL
Wenn man mal mit valgrind reinguckt:
Mit gcc compiliert (mit optimierung):
1
$ valgrind --leak-check=full ./main_g
2
==10425== Memcheck, a memory error detector
3
==10425== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
4
==10425== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
5
==10425== Command: ./main_g
6
==10425==
7
==10425== Use of uninitialised value of size 8
8
==10425== at 0x1091C4: __libc_csu_init (in /home/ghost/devel/cpp_test/main_g)
9
==10425== by 0x109048: main (in /home/ghost/devel/cpp_test/main_g)
10
==10425==
11
==10425==
12
==10425== Process terminating with default action of signal 11 (SIGSEGV): dumping core
13
==10425== Bad permissions for mapped region at address 0x1FFF000000
14
==10425== at 0x1FFF000000: ???
15
==10425== by 0x109048: main (in /home/ghost/devel/cpp_test/main_g)
Rolf M. schrieb:> Und die Frage ist immer, wie aufwändig (wenn> überhaupt möglich) es für den Compiler ist, 100%ig eindeutig> festzustellen, dass UB vorliegt, und das ist meistens gar nicht so> leicht, wie man denkt, bzw. mit sehr viel Code-Analyse verbunden. Aber> nur dann darf er wirklich mit einem Fehler den Compile-Vorgang> abbrechen.
Dürfte er das? "Behaviour" bezieht sich typischerweise auf das Programm,
nicht den Compiler, und zu existieren ist kein Verhalten.
1
undefined behavior - there are no restrictions on the behavior of the program. Examples of undefined behavior are memory accesses outside of array bounds, signed integer overflow, null pointer dereference, modification of the same scalar more than once in an expression without sequence points, access to an object through a pointer of a different type, etc. Compilers are not required to diagnose undefined behavior (although many simple situations are diagnosed), and the compiled program is not required to do anything meaningful.
Heiko L. schrieb:>> Ja, eben. Ich vermute, du unterschätzt diesen Aufwand.> Die Stichproben an Compilern erkennen schon, dass das UB ist.
Wie ich bereits sagte: Du hättest gerne das bestmögliche Verhalten
aktueller Compiler im Standard festgeschrieben. Das halte ich für
unsinnig.
Kaj G. schrieb:> Interessant, dass das Programm sogar aus> unterschiedlichen Gruenden abstuerzt.
Wenn der Compiler zur Sicherheit eine UD2-Instruktion hinlegt, dann
stürzt das Programm zuverlässig und garantiert an der Stelle ab. Wenn
der Compiler das nicht tut, passiert irgendwas, was hoffentlich
irgendwann auch abstürzt, aber möglicherweise vorher noch eine
Sicherheitslücke o.ä. ist.
S. R. schrieb:> Wie ich bereits sagte: Du hättest gerne das bestmögliche Verhalten> aktueller Compiler im Standard festgeschrieben. Das halte ich für> unsinnig.
Und du verstehst es wieder nicht. s.o.
Heiko L. schrieb:> Rolf M. schrieb:>> Und die Frage ist immer, wie aufwändig (wenn>> überhaupt möglich) es für den Compiler ist, 100%ig eindeutig>> festzustellen, dass UB vorliegt, und das ist meistens gar nicht so>> leicht, wie man denkt, bzw. mit sehr viel Code-Analyse verbunden. Aber>> nur dann darf er wirklich mit einem Fehler den Compile-Vorgang>> abbrechen.>> Dürfte er das? "Behaviour" bezieht sich typischerweise auf das Programm,> nicht den Compiler, und zu existieren ist kein Verhalten.undefined> behavior - there are no restrictions on the behavior of the program.> Examples of undefined behavior are memory accesses outside of array> bounds, signed integer overflow, null pointer dereference, modification> of the same scalar more than once in an expression without sequence> points, access to an object through a pointer of a different type, etc.> Compilers are not required to diagnose undefined behavior (although many> simple situations are diagnosed), and the compiled program is not> required to do anything meaningful.
Hab's gefunden. Allerdings nur in einer "Note", also vermutlich nicht
normativ.
behavior for which this International Standard imposes no requirements
[Note:Undefined behavior may be expected when this International
Standard omits any explicit definition ofbehavior or when a program uses
an erroneous construct or erroneous data. Permissible undefined
behaviorranges from ignoring the situation completely with unpredictable
results, to behaving during translation orprogram execution in a
documented manner characteristic of the environment (with or without the
issuance ofa diagnostic message), to terminating a translation or
execution (with the issuance of a diagnostic message).Many erroneous
program constructs do not engender undefined behavior; they are required
to be diagnosed.— end note]
Heiko L. schrieb:> Aber>> nur dann darf er wirklich mit einem Fehler den Compile-Vorgang>> abbrechen.>> Dürfte er das?
Ja. Aus 1.3.12 "undefined behavior" in C++98 (hab grad nix aktuelleres
da):
[Note: permissible 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).
Heiko L. schrieb:> Rolf M. schrieb:>> Und die Frage ist immer, wie aufwändig (wenn>> überhaupt möglich) es für den Compiler ist, 100%ig eindeutig>> festzustellen, dass UB vorliegt, und das ist meistens gar nicht so>> leicht, wie man denkt, bzw. mit sehr viel Code-Analyse verbunden. Aber>> nur dann darf er wirklich mit einem Fehler den Compile-Vorgang>> abbrechen.>> Dürfte er das? "Behaviour" bezieht sich typischerweise auf das Programm,> nicht den Compiler, und zu existieren ist kein Verhalten.undefined> behavior - there are no restrictions on the behavior of the program.> Examples of undefined behavior are memory accesses outside of array> bounds, signed integer overflow, null pointer dereference, modification> of the same scalar more than once in an expression without sequence> points, access to an object through a pointer of a different type, etc.> Compilers are not required to diagnose undefined behavior (although many> simple situations are diagnosed), and the compiled program is not> required to do anything meaningful.
Aus dem c++17 draft (N4687):
1
3.27 [defns.undefined]
2
undefined behavior
3
behavior for which this International Standard imposes no requirements
4
[ Note: Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible 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). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression never exhibits behavior explicitly specified as undefined (8.20). — end note ]
Heiko L. schrieb:> Hab's gefunden. Allerdings nur in einer "Note", also vermutlich nicht> normativ.
Im normativen Teil steht allerdings auch nirgends, dass sich das
"behavior" nur auf die Laufzeit bezieht.
Im Extremfall könnte man auch sagen, dass es auch eine Form von behavior
ist, wenn das Programm nichts tut, weil es nicht existiert. ;-)
Rolf M. schrieb:> Heiko L. schrieb:>> Hab's gefunden. Allerdings nur in einer "Note", also vermutlich nicht>> normativ.>> Im normativen Teil steht allerdings auch nirgends, dass sich das> "behavior" nur auf die Laufzeit bezieht.> Im Extremfall könnte man auch sagen, dass es auch eine Form von behavior> ist, wenn das Programm nichts tut, weil es nicht existiert. ;-)
Dann wäre es aber umso schwieriger zu erklären, warum "ud2" emittiert
werden sollte. Wenn man nicht entscheiden kann, ob der Code da
ausgeführt wird oder nicht und dann dafür sorgt, dass wenn, da eine
Exception generiert wird, ok - allerdings doch nur dann, wenn der
Standard verlangt, UB irgendwie zu übersetzen. Sonst produziert man
mutwillig "garbage".
Heiko L. schrieb:> Dann wäre es aber umso schwieriger zu erklären, warum "ud2" emittiert> werden sollte. Wenn man nicht entscheiden kann, ob der Code da> ausgeführt wird oder nicht und dann dafür sorgt, dass wenn, da eine> Exception generiert wird, ok - allerdings doch nur dann, wenn der> Standard verlangt, UB irgendwie zu übersetzen. Sonst produziert man> mutwillig "garbage".
Hmm, da ist was dran. Das einzige, was mir einfallen würde, wäre, dass
der Compiler nicht weiß, ob foo() überhaupt aufgerufen wird. Wenn nicht,
wird auch kein UB ausgelöst, und dann muss ein funktionierendes Programm
generiert werden.
Wenn ich die Funktion static mache und in main() aufrufe, ändert das
allerdings nicht wirklich was, außer dass foo() wegoptimiert wird und
ud2 direkt für main() generiert wird.
Die Frage ist, ob der Compiler hier main() entsprechend als spezielle
Funktion behandelt, denn dann wüsste er, dass das Programm definitiv
unter allen Umständen UB auslöst, und dann dürfte er tatsächlich das
Übersetzen verweigern.
Edit: Da ist mir grad noch was eingefallen. In C++ muss main() nicht
zwingend aufgerufen werden. Wenn das Programm komplett in Konstruktoren
von globalen Variablen ausgeführt und per exit() beendet wird, käme
main() nie zur Ausführung, und foo() würde nicht aufgerufen.
Das ist es auch, was ich meinte: Es ist selbst in scheinbar trivialen
Fällen meist nicht so einfach, zur Compilezeit zu erkennen, ob mit
100%iger Sicherheit UB ausgelöst wird.
Rolf M. schrieb:> Das ist es auch, was ich meinte: Es ist selbst in scheinbar trivialen> Fällen meist nicht so einfach, zur Compilezeit zu erkennen, ob mit> 100%iger Sicherheit UB ausgelöst wird.
i.A. ist es überhaupt nicht zu erkennen. Angenommen, GCC könnte das:
1
#include<stdlib.h>
2
3
intmain(){
4
if(system("gcc diesesprogramm.cpp")==0){
5
inti=INT_MAX;++i;
6
}else{
7
return0;
8
}
9
}
Wenn das Programm kein UB enthält, läuft der GCC erfolgreich durch, und
das Programm löst UB aus.
Wenn das Programm UB enthält und der GCC das sicher als Fehler erkennt,
beendet sich das Programm ohne UB.
Das ist ein Widerspruch. Das Hauptproblem läst grüßen ;-)
Rolf M. schrieb:> Die Frage ist, ob der Compiler hier main() entsprechend als spezielle> Funktion behandelt, denn dann wüsste er, dass das Programm definitiv> unter allen Umständen UB auslöst, und dann dürfte er tatsächlich das> Übersetzen verweigern.> ...> Es ist selbst in scheinbar trivialen> Fällen meist nicht so einfach, zur Compilezeit zu erkennen, ob mit> 100%iger Sicherheit UB ausgelöst wird.
Ja, richtig. Der einfache Fall von einem "if", bei dem nicht klar ist,
welcher Pfad genommen wird, wäre noch mehr ein Grund, da nicht
vorschnell zu schießen. Wenn man es allerdings weiß, weiß man es.
Rolf M. schrieb:> Edit: Da ist mir grad noch was eingefallen. In C++ muss main() nicht> zwingend aufgerufen werden. Wenn das Programm komplett in Konstruktoren> von globalen Variablen ausgeführt und per exit() beendet wird, käme> main() nie zur Ausführung, und foo() würde nicht aufgerufen.
Hmm, globals könn(t)en auch derferred initialisiert werden. Ich glaube
das Argument ist eher, dass man nicht weiß, ob main() jemals ausgeführt
werden wird. :)
Libraries und so. Es gibt in C++ ja noch nicht einmal einen Weg, Symbole
zu exportieren...
Ob man allerdings
Dr. Sommer schrieb:> Rolf M. schrieb:>> Das ist es auch, was ich meinte: Es ist selbst in scheinbar trivialen>> Fällen meist nicht so einfach, zur Compilezeit zu erkennen, ob mit>> 100%iger Sicherheit UB ausgelöst wird.>> i.A. ist es überhaupt nicht zu erkennen.
Nee, im Konkreten aber schon.