Guten Abend.
Ja, das ist schon die ganze Frage: (Wann) Ist das (nicht) UB ?
...eine der Methoden aufzurufen?
...Und unter welchen Umständen wird es trotzdem relativ gefahrlos
gemacht?
...Und warum spricht -fsanitize=undefined nicht darauf an? Ich dachte,
dazu sei das da!
Einen reinterpret_cast kann man grob gesagt immer dann gefahrlos machen,
wenn das Objekt tatsächlich dem entspricht, in das man es hincastet.
Fast immer gibts aber bessere Wege als reinterpret_cast.
Und wie kommt man in dem Fall dann vorher an etwas, das nicht dem Objekt
entspricht, ohne UB?
Zu dem Fall oben: Warum spricht -fsanitize=undefined nicht darauf an?
Ich dachte,
dazu sei das da!
Man könnte evtl sagen, dass das was da ist, zufällig dem entspricht, was
es sein soll. Kann man das Bsp dann so verändern, dass es nicht mehr
geht?
NaNi schrieb:> (Wann) Ist das (nicht) UB ?> ...eine der Methoden aufzurufen?
Ich würde sagen, eine struct als Array umzuinterpretieren und dann per
Index zuzugreifen ist immer UB. Die zweite Funktion ist soweit ich
verstehe auch immer UB, da man mit dem Zeiger, der aus dem
reintepret_cast rausfällt, nicht viel mehr machen darf, als ihn wieder
in den ursprünglichen Typ zurückzucasten.
Wobei es lustigerweise erlaubt wäre, über eine union zu gehen, sofern T
ein POD ist. Wenn eine union zwei structs enthält, die POD sind und
gleich anfangen, dann ist es erlaubt, auf die entsprechenden Member auch
über die "falsche" Struktur zuzugreifen.
NaNi schrieb:> Warum spricht -fsanitize=undefined nicht darauf an?
Weil das natürlich nicht alle Arten von UB abfangen kann.
> Und wie kommt man in dem Fall dann vorher an etwas, das nicht dem Objekt
entspricht, ohne UB?
Hä?
Hast du einmal versucht das vernünftig mit einem switch zu
implementieren?
Welcher Code wird daraus optimiert? Der Compiler sollte in der Lage sein
zu erkennen, dass die Elemente in einer Reihe liegen und dann per
pointer arithmetik zugreifen können (nach einer initialen
Bereichsabfrage).
Rolf M. schrieb:> Wobei es lustigerweise erlaubt wäre, über eine union zu gehen, sofern T> ein POD ist. Wenn eine union zwei structs enthält, die POD sind und> gleich anfangen, dann ist es erlaubt, auf die entsprechenden Member auch> über die "falsche" Struktur zuzugreifen.
Moment, wie?! Soweit ich weiß, existiert immer nur ein Member eines
Union gleichzeitig.
Wenn sie jetzt ein komplett, statt nur zu Beginn, identisches
Datenlayout hätten, ok, aber was meinst du mit "gleich anfangen"?
Padding z.B. gibts doch auch bei POD.
MaWin schrieb:> Weil das natürlich nicht alle Arten von UB abfangen kann.
Weißt du, ob das die Bedründung ist? Ich meine, könnte sein, aber muss
das der Grund sein? Es scheint Ausnahmen zu geben, dort, wo der Compiler
mit Absicht nicht standardkonform (aber irgendwie noch ISO-xy-konform)
ist.
MaWin schrieb:> Hä?
Ah, schon gut. Denkfehler.
MaWin schrieb:> Hast du einmal versucht das vernünftig mit einem switch zu> implementieren?> Welcher Code wird daraus optimiert? Der Compiler sollte in der Lage sein> zu erkennen, dass die Elemente in einer Reihe liegen und dann per> pointer arithmetik zugreifen können (nach einer initialen> Bereichsabfrage).
Das wollte ich mir eigentlich ersparen. Wenn schon Array, dann auch mit
theoretisch 50k Einträgen..
NaNi schrieb:> Wenn schon Array, dann auch mit> theoretisch 50k Einträgen..
Das kannst du ja machen. Aber dann solltest du es auch komplett
durchziehen und auf das struct ganz verzichten.
MaWin schrieb:> Das kannst du ja machen. Aber dann solltest du es auch komplett> durchziehen und auf das struct ganz verzichten.
Nein.
Ich meinte damit, ich würde mich dabei weniger auf den Compiler
verlassen.
"Sollte erkennen" == UB ...
MaWin schrieb:> Unsinn.
Aber auf "Sollte erkennen" == Konjunktiv ... können wir uns einigen?
MaWin schrieb:> Das kannst du ja machen. Aber dann solltest du es auch komplett> durchziehen und auf das struct ganz verzichten.
Wozu dann noch:
MaWin schrieb:> Hast du einmal versucht das vernünftig mit einem switch zu> implementieren?
??
ich glaube, das ist kein UB. Warum? Siehe hier:
https://en.cppreference.com/w/cpp/numeric/complex (siehe unter
Array-oriented access)
Ich kann mir kaum vorstellen, dass für std::complex hier spezielle
regeln gelten.
NaNi schrieb:> Rolf M. schrieb:>> Wobei es lustigerweise erlaubt wäre, über eine union zu gehen, sofern T>> ein POD ist. Wenn eine union zwei structs enthält, die POD sind und>> gleich anfangen, dann ist es erlaubt, auf die entsprechenden Member auch>> über die "falsche" Struktur zuzugreifen.>> Moment, wie?! Soweit ich weiß, existiert immer nur ein Member eines> Union gleichzeitig.
Im Grunde ja, aber es gibt die von mir beschriebene Ausnahme, die von C
übernommen wurde. Hauptsächlich wohl, um in etwa so was zu ermöglichen:
1
enumevent_type
2
{
3
mouse_move,mouse_click
4
};
5
6
structevent
7
{
8
event_typetype;
9
};
10
11
structmove_event
12
{
13
event_typetype;
14
intx,y;
15
};
16
17
structclick_event
18
{
19
event_typetype;
20
intbutton;
21
};
22
23
unionevent_union
24
{
25
eventevent;
26
move_eventmove;
27
click_eventclick;
28
};
29
30
voidhandle_event(constevent_union*u)
31
{
32
switch(u->event.type)// Zugriff über anderes Element
33
{
34
casemouse_move:
35
// handle u.move
36
break;
37
casemouse_click:
38
// handle u.click
39
break;
40
}
> Wenn sie jetzt ein komplett, statt nur zu Beginn, identisches> Datenlayout hätten, ok, aber was meinst du mit "gleich anfangen"?
Ich meine, dass die ersten paar Member in den Strukturen von Typen sind,
die das gleiche Layout haben (in C++-Sprech "common initial sequence").
Dann hat auch der Teil der Strukturen das gleiche Layout (in C++-Sprech
"layout-compatible"), und man darf auf genau diese auch über die andere
Struktur zugreifen. Hier der Original-Text aus dem C++17-Draft:
"If a standard-layout union contains two or more standard-layout structs
that share a common initial sequence, and if the standard-layout union
object currently contains one of these standard-layout structs, it is
permitted to inspect the common initial part of any of them. Two
standard-layout structs share a common initial sequence if corresponding
members have layout-compatible types and either neither member is a
bit-field or
both are bit-fields with the same width for a sequence of one or more
initial members."
> Padding z.B. gibts doch auch bei POD.
Ja, aber wenn die Member die gleichen sind, ist auch das Padding das
gleiche.
Rolf M. schrieb:> Im Grunde ja, aber es gibt die von mir beschriebene Ausnahme, die von C> übernommen wurde. Hauptsächlich wohl, um in etwa so was zu ermöglichen:
Ah, ja. Jetzt verstehe ich was du meinst. Anscheinend gab es vor c11
keine anonymen unions. Was diese Ausnahme vieleicht nötig gemacht hat.
Wilhelm M. schrieb:> Als Lösung dafür gibt es std::bit_cast (ist auch constexpr).
Sieht so aus, als würde das aber immer eine Kopie zurück geben. Käme
also nicht ganz an unions oder reinterpret_casts dran.
NaNi schrieb:> Rolf M. schrieb:>> Im Grunde ja, aber es gibt die von mir beschriebene Ausnahme, die von C>> übernommen wurde. Hauptsächlich wohl, um in etwa so was zu ermöglichen:>> Ah, ja. Jetzt verstehe ich was du meinst. Anscheinend gab es vor c11> keine anonymen unions. Was diese Ausnahme vieleicht nötig gemacht hat.
Ich kenne zwar auch die o.g. Ausnahme mit der common-initial-sequence
bei unions, aber trotzdem bleibt der Zugriff über das non-aktiv member,
was UB ist. Zumindest scheint der g++ das so zu interpretieren.
Eine der besten Möglichkeiten herauszufinden, ob ein Konstrukt UB ist,
es in einen constexpr-Kontext zu verfrachten (dazu hatte ich vor einiger
Zeit (Jahren) mal Beispiele gemacht, die ich hier zwar auch gepostet
hatte, nun aber gerade auf die Schnelle nicht wiederfinde). Denn in
einem constexpr-Kontext ist kein UB erlaubt -> diagnostic required!
Daher gibt leider doch das obige Konstrukt (im g++) UB:
> Wilhelm M. schrieb:>> Als Lösung dafür gibt es std::bit_cast (ist auch constexpr).>> Sieht so aus, als würde das aber immer eine Kopie zurück geben. Käme> also nicht ganz an unions oder reinterpret_casts dran.
Ich denke, das std::bit_cast hier (meistens) auf die Kopie verzichten
wird, weil es eben genau für diesen Anwendungsfall (als built-in)
gemacht worden ist. Das erkennen des punning-patterns ist genau seine
Aufgabe. Im Gegensatz zu memcpy(), was das zwar auch (oft) erkennt, aber
dann eben auch nicht constexpr ist.
Wie man oben sieht, betrachtet der g++ den Weg über unions (trotz der
Ausnahme im C++-Standard) als UB, und das reinterpret_cast sowieso (was
zudem auch nicht in einem constexpr-Kontext angewendet werden kann).
Wenn Du homogene Datenelemente in den Klassen hast, kannst ggf. ein
Array nehmen. Wenn Du weiterhin eine Basistemplate einführst, dann
erhälst Du folgendes: