Forum: Compiler & IDEs C++ - Warum gibt es hier einen SegFault?


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Hallo,

die Frage geht auf diesen Fehler zurueck:
Beitrag "Re: AVR Simulator mit grafischer Benutzeroberfläche für Linux"

Vereinfachtes Beispiel:
1
int* foo()
2
{
3
  int* r;
4
  r = new int();
5
  //return r;
6
}
7
8
int main(void)
9
{
10
  int* x = foo();
11
12
  return 0;
13
}
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

von Heiko L. (zer0)


Lesenswert?

Wahrscheinlich liegt das an einem Sanitizer.

von mh (Gast)


Lesenswert?

Du hast kein return in foo() und damit undefined behavior. beim gcc 
gibts ne Warnung
1
In function 'int* foo()':
2
warning: no return statement in function returning non-void [-Wreturn-type]

von Dr. Sommer (Gast)


Lesenswert?

Kaj G. schrieb:
> Vereinfachtes Beispiel:

Und du bist ganz sicher dass exakt genau dieser Code eine SegFault 
produziert?

von mh (Gast)


Lesenswert?

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.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Dr. Sommer schrieb:
> Kaj G. schrieb:
>> Vereinfachtes Beispiel:
>
> Und du bist ganz sicher dass exakt genau dieser Code eine SegFault
> produziert?
Ja.
1
$ cat main.cpp
2
int* foo()
3
{
4
        int* r;
5
        r = new int();
6
        //return r;
7
}
8
9
int main(void)
10
{
11
        int* x = foo();
12
13
        return 0;
14
}
15
16
$ g++ -O2 -o main main.cpp && ./main
17
main.cpp: In function 'int* foo()':
18
main.cpp:6:1: warning: no return statement in function returning non-void [-Wreturn-type]
19
 }
20
 ^
21
Segmentation fault (core dumped)

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

mh schrieb:
> undefined behavior
Ja, ergibt Sinn... :-|

von Robert S. (robert_s68)


Lesenswert?

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.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Die Frage ist, wieso das "return r" auskommentiert wurde ...

Compiler halten sich bei hohen Optimierungsstufen nicht an die 
Zeilennummern des Source-Codes...^^

von Dr. Sommer (Gast)


Lesenswert?

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...

von M.K. B. (mkbit)


Lesenswert?

Dies ist ein interessantes Video zum Thema undefined behaviour.
Speziell zu dem Thema kein return ab 29:15.

https://www.youtube.com/watch?v=ehyHyAIa5so

von Heiko L. (zer0)


Lesenswert?

https://godbolt.org/
1
foo():
2
        sub     rsp, 8
3
        mov     edi, 4
4
        call    operator new(unsigned long)
5
main:
6
        sub     rsp, 8
7
        call    foo()

Das return hätte hier wohl echt "ret" geheißen.

von M.K. B. (mkbit)


Lesenswert?

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.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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. :)

von Heiko L. (zer0)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

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. :)

von M.K. B. (mkbit)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

M.K. B. schrieb:
> Undefined behaviour wird ja nicht vom Compiler, sondern vom Standard
> festgelegt.

Ob es das wirklich ist aber nicht.

von S. R. (svenska)


Lesenswert?

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?

von Heiko L. (zer0)


Lesenswert?

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?

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

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".

von M.K. B. (mkbit)


Lesenswert?

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.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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???"

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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

von foobar (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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?

von Dr. Sommer (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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?
;-)

von Heiko L. (zer0)


Lesenswert?

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.

von foobar (Gast)


Lesenswert?

> 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 ...

von Dr. Sommer (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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 ...

von Heiko L. (zer0)


Lesenswert?

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.

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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?

von Heiko L. (zer0)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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?

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

Also: Alles so lassen wie bisher.

von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Also: Alles so lassen wie bisher.

Beim Standard wäre eher das definierte Verhalten das Problem als das 
undefinierte.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Ja, eben. Ich vermute, du unterschätzt diesen Aufwand.

Die Stichproben an Compilern erkennen schon, dass das UB ist.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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)
16
==10425== 
17
==10425== HEAP SUMMARY:
18
==10425==     in use at exit: 4 bytes in 1 blocks
19
==10425==   total heap usage: 2 allocs, 1 frees, 72,708 bytes allocated
20
==10425== 
21
==10425== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
22
==10425==    at 0x4838DEF: operator new(unsigned long) (vg_replace_malloc.c:334)
23
==10425==    by 0x10915D: foo() (in /home/ghost/devel/cpp_test/main_g)
24
==10425==    by 0x109048: main (in /home/ghost/devel/cpp_test/main_g)
25
==10425== 
26
==10425== LEAK SUMMARY:
27
==10425==    definitely lost: 4 bytes in 1 blocks
28
==10425==    indirectly lost: 0 bytes in 0 blocks
29
==10425==      possibly lost: 0 bytes in 0 blocks
30
==10425==    still reachable: 0 bytes in 0 blocks
31
==10425==         suppressed: 0 bytes in 0 blocks


Mit clang compiliert (ohne optimierung):
1
$ valgrind --leak-check=full ./main_c
2
==10477== Memcheck, a memory error detector
3
==10477== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
4
==10477== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
5
==10477== Command: ./main_c
6
==10477== 
7
==10477== valgrind: Unrecognised instruction at address 0x10915f.
8
==10477==    at 0x10915F: foo() (in /home/ghost/devel/cpp_test/main_c)
9
==10477==    by 0x109183: main (in /home/ghost/devel/cpp_test/main_c)
10
==10477== Your program just tried to execute an instruction that Valgrind
11
==10477== did not recognise.  There are two possible reasons for this.
12
==10477== 1. Your program has a bug and erroneously jumped to a non-code
13
==10477==    location.  If you are running Memcheck and you just saw a
14
==10477==    warning about a bad jump, it's probably your program's fault.
15
==10477== 2. The instruction is legitimate but Valgrind doesn't handle it,
16
==10477==    i.e. it's Valgrind's fault.  If you think this is the case or
17
==10477==    you are not sure, please let us know and we'll try to fix it.
18
==10477== Either way, Valgrind will now raise a SIGILL signal which will
19
==10477== probably kill your program.
20
==10477== 
21
==10477== Process terminating with default action of signal 4 (SIGILL): dumping core
22
==10477==  Illegal opcode at address 0x10915F
23
==10477==    at 0x10915F: foo() (in /home/ghost/devel/cpp_test/main_c)
24
==10477==    by 0x109183: main (in /home/ghost/devel/cpp_test/main_c)
25
==10477== 
26
==10477== HEAP SUMMARY:
27
==10477==     in use at exit: 4 bytes in 1 blocks
28
==10477==   total heap usage: 2 allocs, 1 frees, 72,708 bytes allocated
29
==10477== 
30
==10477== LEAK SUMMARY:
31
==10477==    definitely lost: 0 bytes in 0 blocks
32
==10477==    indirectly lost: 0 bytes in 0 blocks
33
==10477==      possibly lost: 0 bytes in 0 blocks
34
==10477==    still reachable: 4 bytes in 1 blocks
35
==10477==         suppressed: 0 bytes in 0 blocks
Interessant, dass das Programm sogar aus unterschiedlichen Gruenden 
abstuerzt.

von Heiko L. (zer0)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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]

von Rolf M. (rmagnus)


Lesenswert?

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).

von mh (Gast)


Lesenswert?

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 ]

von Rolf M. (rmagnus)


Lesenswert?

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. ;-)

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

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".

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

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
int main() {
4
  if (system ("gcc diesesprogramm.cpp") == 0) {
5
    int i = INT_MAX; ++i;
6
  } else {
7
    return 0;
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 ;-)

von Dr. Sommer (Gast)


Lesenswert?

Äh, Halteproblem.

von Heiko L. (zer0)


Lesenswert?

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
1
[[export]]
2
void foo() {
3
 int *x = nullptr;
4
 *x = 1;
5
}
übersetzen sollte? Ich plädiere für "-fgarbage"!

von Heiko L. (zer0)


Lesenswert?

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.

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.