Wie vergleicht ein Programm Boolean-Values?
-
Hast du auch mal überlegt, warum du schreiben kannst:
if (a == b) { ... }
anstelle vonif ((a == b) == true) { ... }
?
Du kannst sogar schreiben:if (((a == b) == true) == true) { ... }
Das kannst du weitertreiben, es wird aber nur komplizierter.isPrime
ist doch bereits ein bool! Das heißt, das Ding ist wahr oder falsch. wahr == wahr ist wahr. Das macht es auch nur komplexer!
-
@wob Perfekte Erklärung! Den Gedankengang hatte ich vorher nicht übertragen, aber jetzt ist es wirklich einfach.
-
unrelated
@hustbaer sagte in Wie vergleicht ein Programm Boolean-Values?:
int* p = static_cast<int*>(malloc(sizeof int));
Das ist doch dasselbe Problem daß wir gerade andernorts haben. Ein
p
der auf etwas zeigt das es nicht gibt.
Ich habe versucht dazu p0593r6 zu lesen aber davon Kopfschmerzen bekommen.
-
@Swordfish Für eingebaute Typen muss es da mMn. eine Ausnahme geben. Ich weiss nicht ob es sie gibt, aber ich weiss dass wenn es sie nicht gibt ein verdammt riesiger Haufen an Code UB hätte. Und das will man denke ich nicht.
-
Ja, will man nicht. Deswegen gibt es wahrscheinlich p0593r6.
-
Ich hatte das eigentlich schon so interepretiert, dass es vom Wording her tatsächlich UB wäre, aber es keinen interessiert, weil das Wording Quatsch war.
Muss man wahrscheinlich abwarten, ob @Columbo über diesen Thread stolpert, er weiß sowas ^^
-
@Mechanics Ganz ehrlich? Wenn er den Durchblick(tm) hätte, hätte er sich schon längst gemeldet ^^
// edit: Was jetzt keine Kritik sein soll ... das Wording muss auf jeden Fall gefixt werden, da blickt doch kein normalsterblicher mehr durch.
-
Ich weiss nicht ob es sie gibt, aber ich weiss dass wenn es sie nicht gibt ein verdammt riesiger Haufen an Code UB hätte. Und das will man denke ich nicht.
Darueber habe ich vor Jahren mit dem Autor auf freenode unterhalten. Er hat natuerlich bekraeftigt das viel existierender Code diese strikten Regeln verletzt. Und das die urspruengliche Idee hinter P0137 war, formelle Regeln aufzustellen, und die Luecken danach zu flicken.†
P0593 loest das Problem mit einem retrograden Ansatz; statt viele Objekte aller moeglichen Typen provisorisch zu kreieren (was wahrscheinlich zu sehr vielen formalen Komplikationen fuehren wuerde), wird nur eines genommen, welches dem Programm definiertes Verhalten zusprechen wuerde. Das funktioniert, weil ein Objekt letztlich nur eine Menge von Bytes mit latenten Eigenschaften ist, und die eigentliche Semantik des Programs durch das Typsystem gewaehrleistet wird. Wenn ein Objekt vom Typ
X
wohldefiniert alsint
angesprochen werden darf, und die Semantik derint
-Operationen vollstaendig befolgt wird, spielt der wahre Typ vonX
keine Rolle mehr. (Von der Tatsache mal abgesehen, dass der Typ wegen strict aliasing eben letztlich doch eingeschraenkt ist, aber ganz eindeutig ist es trotzdem nicht).Und die Regeln wurden doch eindeutig formuliert:
malloc
erzeugt Objekte, und folglich wird ein Objekt vom Typint
erzeugt und ein (kompatibler) Zeiger darauf vom Ausdruck ergeben. Undstatic_cast
behaelt die Assoziation des Zeigers zum Objekt auch bei.† Um es in Yakk's Worte zu fassen: "The horse is out of the barn. Has been for 20 years." (Oder irgendwie sowas. Ich glaub unsere Diskussion wurde geloescht. Und 20 Jahre ist eine krasse Unterschaetzung, denn wir uebernehmen hier letztlich den modus operandi von C, welches in der Form schon seit ~50 Jahren verwendet wird
-
@Columbo
Ja, bloss ist P0593 noch nicht abgesegnetBTW: Weisst du wie man korrekt tut um etwas was man per
new char[]
bekommen und dann zweckentfremdet hat wieder zu löschen? Alsochar* mem = new char[2 * sizeof T]; // Aktiviert die magische auto-Objektifizierung // hier entsteht automagisch das T[] das nötig ist um dem Programm definiertes Verhalten zu geben, // allerdings ohne die Ts zu konstruieren T* arr = reinterpret_cast<T*>(mem); // OK new (&arr[0]) T(args...); // OK new (&arr[1]) T(args...); // OK arr[0].useT(); // OK arr[1].useT(); // OK arr[1].~T(); // OK arr[0].~T(); // OK // Und wie werden wir den Speicher jetzt wieder los?
Meine Vermuting ist
new(arr) std::byte[2 * sizeof T]; // Ändert keine Bytes weil ja kein Initializer, reaktiviert aber die magische auto-Objektifizierung // hier entsteht automagisch das char[] das nötig ist um dem Programm definiertes Verhalten zu geben delete [] reinterpret_cast<char*>(arr); // OK
aber so richtig klar geht das aus dem Proposal mMn. nicht hervor.
-
T* arr = reinterpret_cast<T*>(mem); // OK new (&arr[0]) T(args...); // OK new (&arr[1]) T(args...); // OK arr[0].useT(); // OK arr[1].useT(); // OK
Ist technisch inkorrekt, weil
arr
niemals auf einT
Objekt zeigt. Aber vor allem deshalb, weil auch der Zeiger auf das ersteT
Objekt nicht auf ein Array zeigt;arr + 1
ist ein pointer past-the-end, undarr[1]
damit undefiniert. Allerdings ist das selbe Verhalten invector
zu finden, weshalb diese Regeln letztlich ueberarbeitet werden muessen.Zur eigentlichen Frage bzgl pointer provenance, [new.delete.array]p11:
Preconditions:
ptr
is a null pointer or its value represents the address of a block of memory allocated by an earlier call to a (possibly replaced)operator new[](std::size_t
) [...]A value of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory ([intro.memory]) occupied by the object [...]
Ich interpretiere das wie folgt: Das erste Objekt vom Typ
T
liegt am ersten Byte des durch den frueherenoperator new[]
Aufruf allozierten Speichers. Der Zeigerarr
repraesentiert also die Adresse dieses Speichers. Deshalb ist::operator delete[](arr);
IMO wohldefiniert.
Fuer den
delete[]
-Ausdruck sind die Regeln scheinbar differenzierter.In an array delete expression, the value of the operand of
delete
may be a null pointer value or a pointer value that resulted from a previous array new-expression. If not, the behavior is undefined.Das legt streng genommen aus, dass der Zeiger den gleichen Zustand haben muss wie der, welcher vom zugehörigen
operator new[]
Aufruf ergeben worden ist. Dieser Code:delete [] reinterpret_cast<char*>(arr); // Warum nicht gleich"delete [] mem"?
Ist aber korrekt, weil
arr
mangelslaunder
o.ae. immer noch auf das selbe Array wiemem
zeigt, egal wie oft du den Zeiger castest †. Das hat mit der Zeile darueber aber nichts zu tun, denn sie veraendert den Wert vonarr
nicht, und sie erschafft ueberfluessigerweise einchar
-Array, weil das auf welchesmem
zeigt immer noch existiert: ebenjenes Array stellt den Speicher fuer dieT
Objekte bereit (provides storage for).Und letztlich waere auch das hier ok (ich wuerde meine Hand dafuer nicht hinhalten...):
T* arr = new (mem) T(args...); new (arr + 1) T(args...); delete [] arr;
Der Zeigerwert ist vom placement
new[]
Aufruf, die Adresse ist vom vorherigenoperator new[]()
Aufruf. Clang erhebt hier eine Warnung, daarr
ja nicht mittelsnew[]
kreiert worden ist, aber der Standard schreibt explizit vor, dass nicht die Syntax, sondern der tatsaechliche Typ des Pointees eine Rolle spielt.†
static_cast
aendert den Wert eines Zeigers per se nicht [1][2]:A prvalue of type “pointer to cv
T
”, whereT
is an object type, can be converted to a prvalue of type “pointer to cvvoid
”. The pointer value ([basic.compound]) is unchanged by this conversion.A prvalue of type “pointer to cv1
void
” can be converted to a prvalue of type “pointer to cv2T
” [..]. If the original pointer value represents the addressA
of a byte in memory andA
does not satisfy the alignment requirement ofT
, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an objecta
, and there is an objectb
of typeT
(ignoring cv-qualification) that is pointer-interconvertible witha
, the result is a pointer tob
. Otherwise, the pointer value is unchanged by the conversion.
-
@hustbaer sagte in Wie vergleicht ein Programm Boolean-Values?:
Ja, bloss ist P0593 noch nicht abgesegnet
Soweit ich sehen kann, ist das wording doch schon im draft?
-
@Columbo sagte in Wie vergleicht ein Programm Boolean-Values?:
T* arr = reinterpret_cast<T*>(mem); // OK new (&arr[0]) T(args...); // OK new (&arr[1]) T(args...); // OK arr[0].useT(); // OK arr[1].useT(); // OK
Ist technisch inkorrekt, weil
arr
niemals auf einT
Objekt zeigt. Aber vor allem deshalb, weil auch der Zeiger auf das ersteT
Objekt nicht auf ein Array zeigt;arr + 1
ist ein pointer past-the-end, undarr[1]
damit undefiniert. Allerdings ist das selbe Verhalten invector
zu finden, weshalb diese Regeln letztlich ueberarbeitet werden muessen.Das hab ich in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0593r3.html anders verstanden. Da steht unter "This suggests that the largest set of types we could apply this to is" auch "Array types (with any element type)". Und unmittelbar davor der Absatz
Note that we’re only interested in properties of the object itself here, not of its subobjects. In particular, the above two properties always hold for array types. While creating or destroying array elements might run code, creating the array object (without its elements) does not.
Ich hab das so verstanden dass da wenn nötig auch ein Array automagisch entsteht, nur halt eines wo die eigentlichen Elemente noch nicht konstruiert worden sind.
Dieser Code:
delete [] reinterpret_cast<char*>(arr); // Warum nicht gleich"delete [] mem"?
Ist aber korrekt, weil
arr
mangelslaunder
o.ae. immer noch auf das selbe Array wiemem
zeigt, egal wie oft du den Zeiger castest †. Das hat mit der Zeile darueber aber nichts zu tun, denn sie veraendert den Wert vonarr
nicht, und sie erschafft ueberfluessigerweise einchar
-Array, weil das auf welchesmem
zeigt immer noch existiert: ebenjenes Array stellt den Speicher fuer dieT
Objekte bereit (provides storage for).Diese Auslegung (also dass das "provides storage for"
char
array gleichzeitig mit demT[]
existiert) finde ich gewagt. Wenn das Kommittee das so verstehen will, OK. Aber checken tut das keiner mehr.Ah, ja:
delete [] mem
deswegen nicht, weil manmem
üblicherweise nicht mehr hat wenn man nenvector
o.ä. implementiert. Es sei denn man hebt sich nur denchar*
/std::byte*
auf.Und letztlich waere auch das hier ok (ich wuerde meine Hand dafuer nicht hinhalten...):
T* arr = new (mem) T(args...); new (arr + 1) T(args...); delete [] arr;
Der Zeigerwert ist vom placement
new[]
Aufruf, die Adresse ist vom vorherigenoperator new[]()
Aufruf. Clang erhebt hier eine Warnung, daarr
ja nicht mittelsnew[]
kreiert worden ist, aber der Standard schreibt explizit vor, dass nicht die Syntax, sondern der tatsaechliche Typ des Pointees eine Rolle spielt.Halte ich für noch viel gewagter. Jetzt mal implementierungstechnisch betrachtet: Ist denn garantiert dass das mit dem Ermitteln der Anzahl der Objekte die zerstört werden müssen in dem Fall auch funktioniert? Klar, die vernünftigste Variante wird vermutlich sein nur die Grösse in Bytes für jeden Block abzuspeichern und sich dann die Anzahl der zu zerstörenden Objekte auszurechnen. (Andrerseits erfordert das ne Division, was auch nicht toll ist.) Aber garantiert der Standard das auch irgendwie (wenn dann vermutlich über 1000 Umwege)?