From d1dd0e7eb1e3e4423996ca601293f3bee9b3aa50 Mon Sep 17 00:00:00 2001 From: Timur Doumler Date: Tue, 20 May 2025 16:02:17 +0100 Subject: [PATCH 1/3] [ub] Added all missing ubdefs to bring UB annex in line with P3100R2 list; still missing a few examples --- source/basic.tex | 24 +-- source/classes.tex | 4 +- source/expressions.tex | 8 +- source/ub.tex | 329 ++++++++++++++++++++++++++++++----------- 4 files changed, 261 insertions(+), 104 deletions(-) diff --git a/source/basic.tex b/source/basic.tex index e35bfe841b..66e9786a68 100644 --- a/source/basic.tex +++ b/source/basic.tex @@ -3504,7 +3504,7 @@ and produce a pointer value that points to that object, if that value would result in the program having defined behavior. If no such pointer value would give the program defined behavior, -the behavior of the program is undefined. +the behavior of the program is undefined\ubdef{intro.object.implicit.pointer}. If multiple such pointer values would give the program defined behavior, it is unspecified which such pointer value is produced. @@ -3748,19 +3748,19 @@ if the pointer were of type \tcode{\keyword{void}*} is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The -program has undefined behavior\ubdef{lifetime.outside.pointer} if: +program has undefined behavior if: \begin{itemize} \item - the pointer is used as the operand of a \grammarterm{delete-expression}, + the pointer is used as the operand of a \grammarterm{delete-expression}\ubdef{lifetime.outside.pointer.delete}, \item the pointer is used to access a non-static data member or call a - non-static member function of the object, or + non-static member function of the object\ubdef{lifetime.outside.pointer.member}, or \item the pointer is implicitly converted\iref{conv.ptr} to a pointer - to a virtual base class, or + to a virtual base class\ubdef{lifetime.outside.pointer.virtual}, or \item the pointer is used as the operand of a - \keyword{static_cast}\iref{expr.static.cast}, except when the conversion + \keyword{static_cast}\iref{expr.static.cast}\ubdef{lifetime.outside.pointer.static.cast}, except when the conversion is to pointer to \cv{}~\keyword{void}, or to pointer to \cv{}~\keyword{void} and subsequently to pointer to \cv{}~\keyword{char}, @@ -3768,7 +3768,7 @@ \cv{}~\tcode{std::byte}\iref{cstddef.syn}, or \item the pointer is used as the operand of a - \keyword{dynamic_cast}\iref{expr.dynamic.cast}. + \keyword{dynamic_cast}\iref{expr.dynamic.cast}\ubdef{lifetime.outside.pointer.dynamic.cast}. \end{itemize} \begin{example} \begin{codeblock} @@ -3811,14 +3811,14 @@ a glvalue refers to allocated storage\iref{basic.stc.dynamic.allocation}, and using the properties of the glvalue that do not depend on its value is -well-defined. The program has undefined behavior\ubdef{lifetime.outside.glvalue} if: +well-defined. The program has undefined behavior if: \begin{itemize} -\item the glvalue is used to access the object, or -\item the glvalue is used to call a non-static member function of the object, or -\item the glvalue is bound to a reference to a virtual base class\iref{dcl.init.ref}, or +\item the glvalue is used to access the object\ubdef{lifetime.outside.glvalue.access}, or +\item the glvalue is used to call a non-static member function of the object\ubdef{lifetime.outside.glvalue.member}, or +\item the glvalue is bound to a reference to a virtual base class\iref{dcl.init.ref}\ubdef{lifetime.outside.glvalue.virtual}, or \item the glvalue is used as the operand of a \keyword{dynamic_cast}\iref{expr.dynamic.cast} or as the operand of -\keyword{typeid}. +\keyword{typeid}\ubdef{lifetime.outside.glvalue.dynamic.cast}. \end{itemize} \begin{note} diff --git a/source/classes.tex b/source/classes.tex index 0e8bf61c1c..3e28757e0e 100644 --- a/source/classes.tex +++ b/source/classes.tex @@ -6123,7 +6123,7 @@ indirectly derive from \tcode{B} shall have started and the destruction of these classes shall not have -completed, otherwise the conversion results in undefined behavior. +completed, otherwise the conversion results in undefined behavior\ubdef{class.cdtor.convert.pointer}. To form a pointer to (or access the value of) a direct non-static member of an object \tcode{obj}, @@ -6131,7 +6131,7 @@ \tcode{obj} shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member -value) results in undefined behavior\ubdef{class.cdtor.convert.or.form.pointer}. +value) results in undefined behavior\ubdef{class.cdtor.form.pointer}. \begin{example} \begin{codeblock} struct A { }; diff --git a/source/expressions.tex b/source/expressions.tex index 15b5270059..711ea76401 100644 --- a/source/expressions.tex +++ b/source/expressions.tex @@ -1019,7 +1019,7 @@ \end{note} If the value being converted is outside the range of values that can be represented, the behavior is undefined. -% \ubdef{conv.fpint.int.not.represented} +\ubdef{conv.fpint.int.not.represented} If the source type is \keyword{bool}, the value \keyword{false} is converted to zero and the value \keyword{true} is converted to one. @@ -4199,14 +4199,14 @@ that is within its lifetime or within its period of construction or destruction\iref{class.cdtor}, -the behavior is undefined. +the behavior is undefined.\ubdef{expr.dynamic.cast.pointer.lifetime} If \tcode{v} is a glvalue of type \tcode{U} and \tcode{v} does not refer to an object whose type is similar to \tcode{U} and that is within its lifetime or within its period of construction or destruction, -the behavior is undefined.\ubdef{expr.dynamic.cast.lifetime} +the behavior is undefined.\ubdef{expr.dynamic.cast.glvalue.lifetime} \pnum If \tcode{T} is ``pointer to \cv{} \keyword{void}'', then the result @@ -6156,7 +6156,7 @@ element of the array created by that \grammarterm{new-expression}. Zero-length arrays do not have a first element. \end{footnote} -If not, the behavior is undefined. +If not, the behavior is undefined\ubdef{expr.delete.array.mismatch}. \begin{note} This means that the syntax of the \grammarterm{delete-expression} must match the type of the object allocated by \keyword{new}, not the syntax of the diff --git a/source/ub.tex b/source/ub.tex index fa3e3f42a3..e7edd9dc9f 100644 --- a/source/ub.tex +++ b/source/ub.tex @@ -23,6 +23,26 @@ would result in the program having defined behavior. If no such set of objects would give the program defined behavior, the behavior of the program is undefined. +\pnum +\begin{example} +\begin{codeblock} +TIMUR TODO +\end{codeblock} +\end{example} + + +\pnum +\ubxref{intro.object.implicit.pointer]} +After implicitly creating objects within a specified region of storage, +some operations are described as producing a pointer to a +suitable created object \iref{basic.types}. +These operations select one of the implicitly-created objects +whose address is the address of the start of the region of storage, +and produce a pointer value that points to that object, +if that value would result in the program having defined behavior. +If no such pointer value would give the program defined behavior, +the behavior of the program is undefined. + \pnum \begin{example} \begin{codeblock} @@ -87,54 +107,99 @@ \rSec2[ub.basic.life]{Object lifetime} \pnum -\ubxref{lifetime.outside.pointer} \\ -The behavior of some uses of a pointer -pointing to an object outside its lifetime -are not defined: -\begin{itemize} -\item - the object will be or was of a class type with a non-trivial destructor - and the pointer is used as the operand of a \grammarterm{delete-expression}, -\item - the pointer is used to access a non-static data member or call a - non-static member function of the object, or -\item - the pointer is implicitly converted\iref{conv.ptr} to a pointer - to a virtual base class, or -\item - the pointer is used as the operand of a - \tcode{static_cast}\iref{expr.static.cast}, except when the conversion - is to pointer to \cv{}~\tcode{void}, or to pointer to \cv{}~\tcode{void} - and subsequently to pointer to - \cv{}~\tcode{char}, - \cv{}~\tcode{unsigned char}, or - \cv{}~\tcode{std::byte}\iref{cstddef.syn}, or -\item - the pointer is used as the operand of a - \tcode{dynamic_cast}\iref{expr.dynamic.cast}. -\end{itemize} +\ubxref{lifetime.outside.pointer.delete} +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if the object will be or was of a class type with a non-trivial destructor +and the pointer is used as the operand of a \grammarterm{delete-expression}. \pnum \begin{example} -\begin{codeblock} -struct S { - float f = 0; - ~S() {} -}; + \begin{codeblock} + struct S { + float f = 0; + ~S() {} + }; -float f() { - S s; - S* p1 = &s; - S* p2 = new S; - s.~S(); - p2->~S(); - delete p2; // undefined behavior, operand of delete, lifetime has ended and \tcode{S} - // has a non-trivial destructor - return p1->f; // Undefined behavior, accessing non-static data member after - // end of lifetime -} -\end{codeblock} + float f() { + S* p = new S; + p->~S(); + delete p; // undefined behavior, operand of delete, lifetime has ended and \tcode{S} + // has a non-trivial destructor + } + \end{codeblock} \end{example} + +\pnum +\ubxref{lifetime.outside.pointer.member} +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if the pointer is used to access a non-static data member or call a +non-static member function of the object. + +\pnum +\begin{example} + \begin{codeblock} + struct S { + float f = 0; + }; + + float f() { + S s; + S* p = &s; + s.~S(); + return p->f; // Undefined behavior, accessing non-static data member after + // end of lifetime + } + \end{codeblock} +\end{example} + +\pnum +\ubxref{lifetime.outside.pointer.virtual} +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if pointer is implicitly converted\iref{conv.ptr} to a pointer +to a virtual base class. + +\pnum +\begin{example} + \begin{codeblock} + TIMUR TODO + \end{codeblock} +\end{example} + +\pnum +\ubxref{lifetime.outside.pointer.static.cast} +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if the pointer is used as the operand of a +\tcode{static_cast}, except when the conversion +is to pointer to \cv{}~\tcode{void}, or to pointer to \cv{}~\tcode{void} +and subsequently to pointer to +\cv{}~\tcode{char}, +\cv{}~\tcode{unsigned char}, or +\cv{}~\tcode{std::byte}\iref{cstddef.syn}. + +\pnum +\begin{example} + \begin{codeblock} + TIMUR TODO + \end{codeblock} +\end{example} + +\pnum +\ubxref{lifetime.outside.pointer.dynamic.cast} +For a pointer pointing to an object outside of its lifetime, behavior is +undefined if the pointer is used as the operand of a +\tcode{dynamic_cast}\iref{expr.dynamic.cast}. + +\pnum +\begin{example} + \begin{codeblock} + TIMUR TODO + \end{codeblock} +\end{example} + +% TIMUR TODO: old example below needs to be disentangled and integrated into the +% three example-less cases above, but I don't understand which case it's actually +% supposed to depict? +\pnum \begin{example} \begin{codeblock} #include @@ -171,30 +236,68 @@ \end{example} \pnum -\ubxref{lifetime.outside.glvalue} \\ -The behavior of some uses of a glvalue -that refers to an object outside its lifetime -are not defined. +\ubxref{lifetime.outside.glvalue.access} +Behavior is undefined if a glvalue referring to an object outside of its +lifetime is used to access the object. \pnum \begin{example} \begin{codeblock} -struct A{void f(){} }; - void f() { int x = int{10}; - A a; using T = int; x.~T(); + int y = x; // undefined behavior, glvalue used to access the + // object after the lifetime has ended +} +\end{codeblock} +\end{example} + +\pnum +\ubxref{lifetime.outside.glvalue.member} +Behavior is undefined if a glvalue referring to an object outside of its +lifetime is used to call a non-static member function of the object. + +\pnum +\begin{example} +\begin{codeblock} +struct A{ + void f() {} +}; + +void f() { + A a; a.~A(); a.f(); // undefined behavior, glvalue used to access a - // non-static member function after the lifetime has ended - int y = x; // undefined behavior, glvalue used to access the - // object after the lifetime has ended + // non-static member function after the lifetime has ended } \end{codeblock} \end{example} +\pnum +\ubxref{lifetime.outside.glvalue.virtual} +Behavior is undefined if a glvalue referring to an object outside of its +lifetime is bound to a reference to a virtual base class. + +\pnum +\begin{example} + \begin{codeblock} + TIMUR TODO + \end{codeblock} +\end{example} + +\pnum +\ubxref{lifetime.outside.glvalue.dynamic.cast} +Behavior is undefined if a glvalue referring to an object outside of its +lifetime is used as the operand of a +\keyword{dynamic_cast} or as the operand of \keyword{typeid}. + +\pnum +\begin{example} + \begin{codeblock} + TIMUR TODO + \end{codeblock} +\end{example} \pnum \ubxref{original.type.implicit.destructor} \\ @@ -602,8 +705,7 @@ \rSec2[ub.conv.fpint]{Floating-integral conversions} \pnum -\ubxref{conv.fpint.float.not.represented} \\ -%\ubxref{conv.fpint.int.not.represented} \\ % in same section +\ubxref{conv.fpint.int.not.represented} \\ When converting a floating-point value to an integer type and vice versa if the value is not representable in the destination type it is undefined behavior. @@ -617,15 +719,28 @@ // 2,147,483,647 Assuming 32-bit float and 64-bit double double d = (double)std::numeric_limits::max() + 1; int x1 = d; // undefined behavior 2,147,483,647 + 1 is not representable as int - - __uint128_t x2 = -1; - float f = x2; // undefined behavior on systems where the range of - // representable values of float is [-max,+max] on system where - // represetable values are [-inf,+inf] this would not be UB } \end{codeblock} \end{example} +\pnum +\ubxref{conv.fpint.float.not.represented} \\ +When converting a value of integer or unscoped enumeration type to a +floating-point type, if the value is not representable in the destination type +it is undefined behavior. + +\pnum +\begin{example} + \begin{codeblock} + int main() { + __uint128_t x2 = -1; + float f = x2; // undefined behavior on systems where the range of + // representable values of float is [-max,+max] on system where + // represetable values are [-inf,+inf] this would not be UB + } + \end{codeblock} +\end{example} + \rSec2[ub.conv.ptr]{Pointer conversions} \pnum @@ -725,10 +840,29 @@ \rSec2[ub.expr.dynamic.cast]{Dynamic cast} + +\pnum +\ubxref{expr.dynamic.cast.pointer.lifetime} \\ +Evaluating a \keyword{dynamic_cast} on a non-null pointer that points to +an object (of polymorphic type) of the wrong type or to an object +not within its lifetime has undefined behavior. + +\pnum +\begin{example} + \begin{codeblock} + struct B { virtual ~B(); }; + void f() { + B bs[1]; + B* dp = dynamic_cast(bs+1); // undefined behavior + } + \end{codeblock} +\end{example} + + \pnum -\ubxref{expr.dynamic.cast.lifetime} \\ -Evaluating a \keyword{dynamic_cast} on a non-null pointer or reference that -denotes an object (of polymorphic type) of the wrong type or that is +\ubxref{expr.dynamic.cast.glvalue.lifetime} \\ +Evaluating a \keyword{dynamic_cast} on a reference that +denotes an object (of polymorphic type) of the wrong type or an object not within its lifetime has undefined behavior. \pnum @@ -737,7 +871,6 @@ struct B { virtual ~B(); }; void f() { B bs[1]; - B* dp = dynamic_cast(bs+1); // undefined behavior B& dr = dynamic_cast(bs[1]); // undefined behavior } \end{codeblock} @@ -906,7 +1039,7 @@ \pnum \ubxref{expr.delete.mismatch} \\ -Using array delete on the result of a single object new expression and vice versa is undefined behavior. +Using array delete on the result of a single object new expression is undefined behavior. \pnum \begin{example} @@ -916,6 +1049,18 @@ \end{codeblock} \end{example} +\pnum +\ubxref{expr.delete.mismatch} \\ +Using single object delete on the result of an array new expression is undefined behavior. + +\pnum +\begin{example} + \begin{codeblock} + int* x = new int[10]; + delete x; // undefined behavior, allocated using array new expression + \end{codeblock} +\end{example} + \pnum \ubxref{expr.delete.dynamic.type.differ} \\ @@ -1681,35 +1826,47 @@ \end{codeblock} \end{example} +\pnum +\ubxref{class.cdtor.convert.pointer} \\ +When converting a pointer to a base class of an object, +construction must have started and destruction must not have finished otherwise +this is undefined behavior. + +\pnum +\begin{example} + \begin{codeblock} + struct A {}; + struct B : virtual A {}; + struct C : B {}; + struct D : virtual A { + D(A *); + }; + struct X { + X(A *); + }; + struct E : C, D, X { + E() + : D(this), // undefined: upcast from \tcode{E*} to \tcode{A*} might use path E->D->A + // but \tcode{D} is not constructed + // \tcode{D((C*)this)} would be defined: E->C is defined because + // \tcode{E()} has started, and C->A is defined because \tcode{C} is fully + // constructed + X(this) // well-defined: upon construction of \tcode{X}, C/B/D/A sublattice is fully constructed + {} + }; + \end{codeblock} +\end{example} \pnum -\ubxref{class.cdtor.convert.or.form.pointer} \\ -When converting a pointer to a base class of an object or forming a pointer to a direct non-static +\ubxref{class.cdtor.form.pointer} \\ +When forming a pointer to a direct non-static member of a class, construction must have started and destruction must not have finished otherwise this is undefined behavior. \pnum \begin{example} \begin{codeblock} -struct A {}; -struct B : virtual A {}; -struct C : B {}; -struct D : virtual A { - D(A *); -}; -struct X { - X(A *); -}; -struct E : C, D, X { - E() - : D(this), // undefined: upcast from \tcode{E*} to \tcode{A*} might use path E->D->A - // but \tcode{D} is not constructed - // \tcode{D((C*)this)} would be defined: E->C is defined because - // \tcode{E()} has started, and C->A is defined because \tcode{C} is fully - // constructed - X(this) // well-defined: upon construction of \tcode{X}, C/B/D/A sublattice is fully constructed - {} -}; +TIMUR TODO \end{codeblock} \end{example} From 5cfd87a49f864d46454ae58c6ac047de8c4f7260 Mon Sep 17 00:00:00 2001 From: jberne4 Date: Tue, 20 May 2025 15:07:09 -0400 Subject: [PATCH 2/3] [ub] fixups: missing examples, cleaned up formatting --- source/ub.tex | 250 ++++++++++++++++++++++++++------------------------ 1 file changed, 131 insertions(+), 119 deletions(-) diff --git a/source/ub.tex b/source/ub.tex index e7edd9dc9f..8dd7e6f31a 100644 --- a/source/ub.tex +++ b/source/ub.tex @@ -26,13 +26,19 @@ \pnum \begin{example} \begin{codeblock} -TIMUR TODO +void f() +{ + void *p = malloc(sizeof(int) + sizeof(float)); // undefined behavior, cannot create + // both int and float in same place + int& i = *reinterpret_cast(p); + float& f = *reinterpret_cast(p); +} \end{codeblock} \end{example} \pnum -\ubxref{intro.object.implicit.pointer]} +\ubxref{intro.object.implicit.pointer} After implicitly creating objects within a specified region of storage, some operations are described as producing a pointer to a suitable created object \iref{basic.types}. @@ -114,19 +120,19 @@ \pnum \begin{example} - \begin{codeblock} - struct S { - float f = 0; - ~S() {} - }; +\begin{codeblock} +struct S { + float f = 0; + ~S() {} +}; - float f() { - S* p = new S; - p->~S(); - delete p; // undefined behavior, operand of delete, lifetime has ended and \tcode{S} - // has a non-trivial destructor - } - \end{codeblock} +float f() { + S* p = new S; + p->~S(); + delete p; // undefined behavior, operand of delete, lifetime has ended and \tcode{S} + // has a non-trivial destructor +} +\end{codeblock} \end{example} \pnum @@ -137,19 +143,19 @@ \pnum \begin{example} - \begin{codeblock} - struct S { - float f = 0; - }; +\begin{codeblock} +struct S { + float f = 0; +}; - float f() { - S s; - S* p = &s; - s.~S(); - return p->f; // Undefined behavior, accessing non-static data member after - // end of lifetime - } - \end{codeblock} +float f() { + S s; + S* p = &s; + s.~S(); + return p->f; // undefined behavior, accessing non-static data member after + // end of lifetime +} +\end{codeblock} \end{example} \pnum @@ -160,9 +166,16 @@ \pnum \begin{example} - \begin{codeblock} - TIMUR TODO - \end{codeblock} +\begin{codeblock} +struct B {}; +struct D : virtual B {}; +void f() { + D d; + D* p = &d; + d.~D(); + B* b = p; // undefined behavior +} +\end{codeblock} \end{example} \pnum @@ -178,9 +191,18 @@ \pnum \begin{example} - \begin{codeblock} - TIMUR TODO - \end{codeblock} +\begin{codeblock} +struct B {}; +struct D : B {}; +void f() +{ + D d; + B* bp = &d; + d.~D(); + void* vp = bp; // OK + D* dp = static_cast(bp); // undefined behavior +} +\end{codeblock} \end{example} \pnum @@ -189,48 +211,17 @@ undefined if the pointer is used as the operand of a \tcode{dynamic_cast}\iref{expr.dynamic.cast}. -\pnum -\begin{example} - \begin{codeblock} - TIMUR TODO - \end{codeblock} -\end{example} - -% TIMUR TODO: old example below needs to be disentangled and integrated into the -% three example-less cases above, but I don't understand which case it's actually -% supposed to depict? \pnum \begin{example} \begin{codeblock} -#include -#include - -struct B { - virtual void f(); - void mutate(); - virtual ~B(); -}; - -struct D1 : B { - void f(); -}; -struct D2 : B { - void f(); -}; - -void B::mutate() { - new (this) D2; // reuses storage and ends the lifetime of *this - f(); // undefined behavior - B *b = this; // OK, this points to valid memory -} - -void g() { - void* p = std::malloc(sizeof(D1) + sizeof(D2)); - B* pb = new (p) D1; - pb->mutate(); - *pb; // OK, pb points to valid memory - void* q = pb; // OK, pb points to valid memory - pb->f(); // undefined behavior, lifetime of \tcode{*pb} has ended +struct B { virtual ~B() = default; }; +struct D : B {}; +void f() +{ + D d; + B* bp = &d; + d.~D(); + D* dp = dynamic_cast(bp); // undefined behavior } \end{codeblock} \end{example} @@ -248,7 +239,7 @@ using T = int; x.~T(); int y = x; // undefined behavior, glvalue used to access the - // object after the lifetime has ended + // object after the lifetime has ended } \end{codeblock} \end{example} @@ -261,15 +252,15 @@ \pnum \begin{example} \begin{codeblock} -struct A{ +struct A { void f() {} }; void f() { A a; a.~A(); - a.f(); // undefined behavior, glvalue used to access a - // non-static member function after the lifetime has ended + a.f(); // undefined behavior, glvalue used to access a + // non-static member function after the lifetime has ended } \end{codeblock} \end{example} @@ -281,9 +272,17 @@ \pnum \begin{example} - \begin{codeblock} - TIMUR TODO - \end{codeblock} +\begin{codeblock} +struct B {}; +struct D : virtual B { +}; + +void f() { + D d; + d.~D(); + B& b = d; // undefined behavior +} +\end{codeblock} \end{example} \pnum @@ -294,9 +293,17 @@ \pnum \begin{example} - \begin{codeblock} - TIMUR TODO - \end{codeblock} +\begin{codeblock} +struct B { virtual ~B(); }; +struct D : virtual B {}; + +void f() { + D d; + B& br = d; + d.~D(); + D& dr = dynamic_cast(br); // undefined behavior +} +\end{codeblock} \end{example} \pnum @@ -491,7 +498,7 @@ int count = 0; auto f = [&] { count++; }; std::thread t1{f}, t2{f}, t3{f}; -// Undefined behavior t1, t2 and t3 have a data race on access of variable count +// undefined behavior t1, t2 and t3 have a data race on access of variable count \end{codeblock} \end{example} @@ -731,14 +738,14 @@ \pnum \begin{example} - \begin{codeblock} +\begin{codeblock} int main() { __uint128_t x2 = -1; float f = x2; // undefined behavior on systems where the range of // representable values of float is [-max,+max] on system where // represetable values are [-inf,+inf] this would not be UB } - \end{codeblock} +\end{codeblock} \end{example} \rSec2[ub.conv.ptr]{Pointer conversions} @@ -849,13 +856,13 @@ \pnum \begin{example} - \begin{codeblock} +\begin{codeblock} struct B { virtual ~B(); }; void f() { B bs[1]; B* dp = dynamic_cast(bs+1); // undefined behavior } - \end{codeblock} +\end{codeblock} \end{example} @@ -1045,20 +1052,20 @@ \begin{example} \begin{codeblock} int* x = new int; -delete[] x; // undefined behavior, allocated using single object new expression +delete[] x; // undefined behavior, allocated using single object new expression \end{codeblock} \end{example} \pnum -\ubxref{expr.delete.mismatch} \\ +\ubxref{expr.delete.array.mismatch} \\ Using single object delete on the result of an array new expression is undefined behavior. \pnum \begin{example} - \begin{codeblock} - int* x = new int[10]; - delete x; // undefined behavior, allocated using array new expression - \end{codeblock} +\begin{codeblock} +int* x = new int[10]; +delete x; // undefined behavior, allocated using array new expression +\end{codeblock} \end{example} @@ -1834,39 +1841,44 @@ \pnum \begin{example} - \begin{codeblock} - struct A {}; - struct B : virtual A {}; - struct C : B {}; - struct D : virtual A { - D(A *); - }; - struct X { - X(A *); - }; - struct E : C, D, X { - E() - : D(this), // undefined: upcast from \tcode{E*} to \tcode{A*} might use path E->D->A - // but \tcode{D} is not constructed - // \tcode{D((C*)this)} would be defined: E->C is defined because - // \tcode{E()} has started, and C->A is defined because \tcode{C} is fully - // constructed - X(this) // well-defined: upon construction of \tcode{X}, C/B/D/A sublattice is fully constructed - {} - }; - \end{codeblock} +\begin{codeblock} +struct A { }; +struct B : virtual A { }; +struct C : B { }; +struct D : virtual A { D(A*); }; +struct X { X(A*); }; + +struct E : C, D, X { + E() : D(this), // undefined behavior: upcast from \tcode{E*} to \tcode{A*} might use path \tcode{E*} $\rightarrow$ \tcode{D*} $\rightarrow$ \tcode{A*} + // but \tcode{D} is not constructed + + // ``\tcode{D((C*)this)}\!'' would be defined: \tcode{E*} $\rightarrow$ \tcode{C*} is defined because \tcode{E()} has started, + // and \tcode{C*} $\rightarrow$ \tcode{A*} is defined because \tcode{C} is fully constructed + + X(this) {} // defined: upon construction of \tcode{X}, \tcode{C/B/D/A} sublattice is fully constructed +}; +\end{codeblock} \end{example} \pnum \ubxref{class.cdtor.form.pointer} \\ -When forming a pointer to a direct non-static -member of a class, construction must have started and destruction must not have finished otherwise -this is undefined behavior. +When forming a pointer to +a direct non-static member of a class, +construction must have started +and destruction must not have finished +otherwise the behavior is undefined. \pnum \begin{example} \begin{codeblock} -TIMUR TODO +struct A { + int i = 0; +}; +struct B { + int *p; + A a; + B() : p(&a.a) {} // undefined behavior +}; \end{codeblock} \end{example} @@ -2017,7 +2029,7 @@ struct A { A() try : x(0 ? 1 : throw 1) { } catch (int) { - std::cout << "y: " << y << std::endl; // Undefined behavior, referring to non-static member y in + std::cout << "y: " << y << std::endl; // undefined behavior, referring to non-static member y in // the handler of function-try-block } int x; @@ -2120,7 +2132,7 @@ #define cat(x, y) x##y void f() { - cat(/, /) // Undefined behavior // is not a valid preprocessing token + cat(/, /) // undefined behavior // is not a valid preprocessing token } \end{codeblock} \end{example} From 31fbb5f5de6dec341549cccf5bfc61c3b94eb0c7 Mon Sep 17 00:00:00 2001 From: Timur Doumler Date: Wed, 21 May 2025 08:09:38 +0100 Subject: [PATCH 3/3] [ub] minor fix in one example --- source/ub.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ub.tex b/source/ub.tex index 8dd7e6f31a..988cf8e43c 100644 --- a/source/ub.tex +++ b/source/ub.tex @@ -1877,7 +1877,7 @@ struct B { int *p; A a; - B() : p(&a.a) {} // undefined behavior + B() : p(&a.i) {} // undefined behavior }; \end{codeblock} \end{example}