#include <new>
struct B { int i; };
extern struct X : B { X() {} } xobj;
struct Y { int j = *std::launder((int*)(&xobj)) += 42; } yobj;
X xobj;
#include <new>
struct B { int i; };
struct X : B { X() {} };
struct Z { int k = *std::launder((int*)(&k + 1)) += 42; X x; } zobj;
#include <cstdio>
#include <cstring>
inline int read(void const* p) { int i; memcpy(&i, p, 4); return i; }
inline int write(void* p, int const& i) { memcpy(p, &i, 4); return i; }
struct B { int i; };
struct X : B { X() {} };
extern X xobj;
struct Y { int j; Y() : j(write(&xobj, read(&xobj) + 42)) {} } yobj;
X xobj;
struct Z { int k; X x; Z() : k(write(&k + 1, read(&k + 1) + 42)) {} } zobj;
int main() { printf("%d %d\n", xobj.i, zobj.x.i); }
On quinta-feira, 11 de agosto de 2016 12:09:27 PDT Edward Catmur wrote:
> Does this program have defined behavior?
>
> #include <new>
> struct B { int i; };
> extern struct X : B { X() {} } xobj;
> struct Y { int j = *std::launder((int*)(&xobj)) += 42; } yobj;
> X xobj;
>
> At the constructor of yobj.j, "referring to any non-static member or base"
> of xobj is UB, so we cannot write xobj.B::i; but that sub-subobject has
> begun its lifetime, is not itself a non-static member or base of an object
> with non-trivial initialization (it is a member of B, which has a trivial
> constructor), and we know precisely where its storage is located. So can we
> launder (read/write) access to it?
The above Y constructor is equivalent to:
struct *B = &xobj; // accesses base-class B of X, theoretically UB
j = b->i += 42;
But both X and B are trivially constructible and standard layout, like you
said.
> Similarly, an earlier member may not be allowed to refer to a subobject of
> a later member, but it may know at which storage location a sub-subobject
> of that later member lives:
>
> #include <new>
> struct B { int i; };
> struct X : B { X() {} };
> struct Z { int k = *std::launder((int*)(&k + 1)) += 42; X x; } zobj;
>
> I'm not aware of any publicly available compilers with std::launder support
> available yet, but using [basic.types]/3 I have been able to ascertain that
> gcc regards this as UB while other compilers (clang, ICC, MSVC) apparently
> do not (they initialize xobj.i, zobj.x.i each to 42 with nary a complaint).
Well, since you're depending on behaviour not yet implemented, we can't say
much about what compilers are doing or not doing.
> C++03-compatible test program (both cases):
>
> #include <cstdio>
> #include <cstring>
> inline int read(void const* p) { int i; memcpy(&i, p, 4); return i; }
> inline int write(void* p, int const& i) { memcpy(p, &i, 4); return i; }
> struct B { int i; };
> struct X : B { X() {} };
> extern X xobj;
> struct Y { int j; Y() : j(write(&xobj, read(&xobj) + 42)) {} } yobj;
> X xobj;
> struct Z { int k; X x; Z() : k(write(&k + 1, read(&k + 1) + 42)) {} } zobj;
> int main() { printf("%d %d\n", xobj.i, zobj.x.i); }
Under C++03 rules, this is definitely UB. There's no such thing as standard
layout, so all we can say is that Y and Z are non-POD, therefore both your
reads and writes are UB.
Does this program have defined behavior?
#include <new>
struct B { int i; };
extern struct X : B { X() {} } xobj;
struct Y { int j = *std::launder((int*)(&xobj)) += 42; } yobj;
X xobj;At the constructor of yobj.j, "referring to any non-static member or base" of xobj is UB, so we cannot write xobj.B::i; but that sub-subobject has begun its lifetime, is not itself a non-static member or base of an object with non-trivial initialization (it is a member of B, which has a trivial constructor), and we know precisely where its storage is located. So can we launder (read/write) access to it?
Similarly, an earlier member may not be allowed to refer to a subobject of a later member, but it may know at which storage location a sub-subobject of that later member lives:
#include <new>
struct B { int i; };
struct X : B { X() {} };
struct Z { int k = *std::launder((int*)(&k + 1)) += 42; X x; } zobj;I'm not aware of any publicly available compilers with std::launder support available yet, but using [basic.types]/3 I have been able to ascertain that gcc regards this as UB while other compilers (clang, ICC, MSVC) apparently do not (they initialize xobj.i, zobj.x.i each to 42 with nary a complaint).C++03-compatible test program (both cases):
#include <cstdio>
#include <cstring>
inline int read(void const* p) { int i; memcpy(&i, p, 4); return i; }
inline int write(void* p, int const& i) { memcpy(p, &i, 4); return i; }
struct B { int i; };
struct X : B { X() {} };
extern X xobj;
struct Y { int j; Y() : j(write(&xobj, read(&xobj) + 42)) {} } yobj;
X xobj;
struct Z { int k; X x; Z() : k(write(&k + 1, read(&k + 1) + 42)) {} } zobj;
int main() { printf("%d %d\n", xobj.i, zobj.x.i); }
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
On Thu, Aug 11, 2016 at 12:09 PM, Edward Catmur <e...@catmur.co.uk> wrote:Does this program have defined behavior?
#include <new>
struct B { int i; };
extern struct X : B { X() {} } xobj;
struct Y { int j = *std::launder((int*)(&xobj)) += 42; } yobj;
X xobj;At the constructor of yobj.j, "referring to any non-static member or base" of xobj is UB, so we cannot write xobj.B::i; but that sub-subobject has begun its lifetime, is not itself a non-static member or base of an object with non-trivial initialization (it is a member of B, which has a trivial constructor), and we know precisely where its storage is located. So can we launder (read/write) access to it?[class.cdtor]/1 seems pretty vague here. It might be talking about class member access and derived-to-base conversions, or it might be talking about performing an access (read / modification) on the corresponding subobject, or something else. But probably the most plausible reading is that it means that any of these actions that could reasonably be considered to be referencing the non-static member or base results in UB (that is, it doesn't matter what concrete syntax you use to get at the non-static member or base).
On quinta-feira, 11 de agosto de 2016 23:03:40 PDT 'Edward Catmur' via ISO C++
Standard - Discussion wrote:
> On Thu, Aug 11, 2016 at 10:46 PM, Thiago Macieira <thi...@macieira.org>
> wrote:
> > The above Y constructor is equivalent to:
> > struct *B = &xobj; // accesses base-class B of X,
> >
> > theoretically UB
> >
> > j = b->i += 42;
> >
> > But both X and B are trivially constructible and standard layout, like you
> > said.
I need to correct myself: B is trivially constructible, but X isn't.
> Sorry, I'm not clear why that's equivalent; the point of using std::launder
> was to avoid the upcast. Without the upcast, is it still UB?
Doesn't matter what you laundered if the compiler assumes you didn't write.
> > Well, since you're depending on behaviour not yet implemented, we can't
> > say
> > much about what compilers are doing or not doing.
>
> Ah, I forgot to mention: the assumption would be that if std::launder is
> not available then the implementation precedes P0137, so the "point to"
> rule in [basic.compound]/3 can be relied on.
I don't see the point. This goes back to the discussion on the other thread
about what happens if you write to the storage of an object before its
lifetime begins. Until that is resolved, this discussion is moot.
On sexta-feira, 12 de agosto de 2016 14:47:31 PDT 'Edward Catmur' via ISO C++
Standard - Discussion wrote:
> > Doesn't matter what you laundered if the compiler assumes you didn't
> > write.
>
> Why would it be entitled to make that assumption? std::launder is described
> as an optimization barrier.
Yes. It may have to discard all known values to all variables, but that
doesn't mean anything about memory it hasn't begun to use. For example,
imagine that you used std::launder to write to a memory location of an unborn
stack variable:
int x = 0;
*std::launder((&x)[-1]) = 1;
int y;
assert(y == 1);
Is the compiler required to make that assertion pass? I'd say not, not even if
I moved the y declaration above the launder.
This is where the discussion of storage area values vs constructors comes in.
If the compiler is allowed to assume that the storage area doesn't contain
anything useful except for static-lifetime objects, then it can assume you
didn't launder something.
> > I don't see the point. This goes back to the discussion on the other
> > thread
> > about what happens if you write to the storage of an object before its
> > lifetime begins. Until that is resolved, this discussion is moot.
>
> Writing to the storage of a composite object before its lifetime begins is
> not a problem, as long as that composite object's constructor has been
> entered ([class.cdtor]/1). Here I am exploring a situation where a
> composite object's constructor has not yet been entered, but its
> initialization has begun and the lifetime of a nested primitive subobject
> has begun. The motivation is to tease apart the concepts of storage
> duration, lifetime, initialization and construction.
Indeed. That is what I'd like to see concluded. std::launder is a distracting
discussion.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-discussion/XYvVlTc3-to/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-discussion+unsubscribe@isocpp.org.
On sexta-feira, 12 de agosto de 2016 18:21:43 PDT 'Edward Catmur' via ISO C++
Standard - Discussion wrote:
> > If the compiler is allowed to assume that the storage area doesn't contain
> > anything useful except for static-lifetime objects, then it can assume you
> > didn't launder something.
>
> But that's just it; I am writing via a pointer that can be used to reach a
> static-lifetime object located in the storage area, so the compiler has to
> assume that my use of launder could modify any of the objects whose
> lifetime has begun at that point.
The problem we're facing here is that a complex objects have complex rules for
the beginning of lifetime. When does the lifetime of a sub-object begin, when
its constructor starts or when the whole object's constructor does?
Also, the only acceptable value for the storage of static-lifetime objects is
zero: they are zero-initialised prior to dynamic construction. Trivially-
constructible objects can be value-initialised after the zeroing pass, but
that's not the case here.
Yes, I think so. That reinterpret_cast is illegal because &xobj cast to int* does not point to an object of type int. It would be legal in C++14 and previous versions, where a pointer automatically points to an object whose address is the same as the address held by the pointer.
std::launder works not only when storage is reused, but whenever two objects occupy the same storage location and are mutually reachable; there is no requirement that they have disjoint lifetimes.
> std::launder works not only when storage is reused, but whenever two
> objects occupy the same storage location and are mutually reachable; there
> is no requirement that they have disjoint lifetimes.
Assuming the pointer-interconvertible rule covers the cases like your
code, "An array object and its first element" shown in the note may be
the only such case.
assert(pb == static_cast<void*>(&d));
// Given pb represents the address of the first byte of d,
// what rule invalidates this access?
return pb->i;
}
I can't locate such a rule that invalidates the last pb-i access.
Could you or someone else?
My interpretation:
The result of reinterpret_cast<Base*>(&d), which is the value of pb, is
defined as static_cast<Base*>(static_cast<void*>(&d)) (in 5.2.10 p7),
and the both static_cast are defined as "the pointer value is unchanged
by the conversion" (4.11 p2, 5.2.9 p13) then the result points to d as
&d does. When the assertion "pb == ..." fails, the access is invalid
because of the aliasing rule (3.10 p8). But if the assertion succeeds
(which means the Base subobject happened to be at the start of the
Derived object) the access seems to be allowed by the aliasing rule as
an access to Derived object through a glvalue of Base.
As "pb points d" is very strange outcome, I think I'm missing some
existing rule. Sorry if it is an obvious one.
--
k_satoda
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.