<div dir="ltr">I'd like to complicate things further, in response to the idea that the snippet involving 'B' and 'short' is type-punning and that we should consider a type-safe mode. :)<br><br>First, consider whether the following code, intended to be completely normal C code:<br>
<br><font face="courier new, monospace"> B* pb = (B*)malloc(sizeof(B));<br> pb->i = 0;<br> free(pb);<br> short* ps = (short*)malloc(sizeof(short));<br> *ps = 0;<br> free(ps);</font><br><br>looks like code that should be valid in this type-safe mode. If you'd like to ban it, the rest of this post won't have anything interesting for you.<br>
<br>Now imagine I write a library (please forgive compile and logic errors, and typos):<br><br><font face="courier new, monospace">std::map<size_t, std::stack<void*>> size_classes = {{16, {}}, {32, {}}, ...};<br>
<br>void* my_malloc(size_t size) {<br> auto size_class = size_classes.lower_bound(size);<br> assert(size_class != size_classes.end());<br> if (size_class->second.empty())<br> return malloc(size_class->first);<br>
void* result = size_class->second.top();<br> size_class->second.pop();<br> return result;<br>}<br><br>void my_free(size_t size, void* block) {<br> size_classes.lower_bound(size)->second.push(block);<br>}</font><br>
<br>Then I use it like:<br><br><font face="courier new, monospace"> B* pb = (B*)my_malloc(sizeof(B));<br> pb->i = 0;<br> my_free(sizeof(B), pb);<br> short* ps = (short*)my_malloc(sizeof(short));<br> *ps = 0;<br> my_free(sizeof(short), ps);</font><br>
<br>Is this worse than the above malloc/free-based code? That is, can users write wrappers around malloc and free?<br><br>But the compiler can inline the my_malloc use down to:<br><br><font face="courier new, monospace"> void *p = malloc(16); // Probably 16.<br>
B* pb = (B*)p;<br> pb->i = 0;<br> short* ps = (short*)pb;<br> *ps = 0;</font><br><br>using knowledge of the behavior of std::map and std::stack, which is nearly identical to the "type-punning" code. So how do we make the type-punning invalid without breaking standard malloc-based code or user-written libraries?<br>
<br>On Fri, Jan 17, 2014 at 2:56 PM, Herb Sutter <<a href="mailto:hsutter@microsoft.com">hsutter@microsoft.com</a>> wrote:<br>>> Note that this post from Herb arrived after<br>>> <a href="http://www.open-std.org/pipermail/ub/2014-January/000418.html">http://www.open-std.org/pipermail/ub/2014-January/000418.html</a> but was sent<br>
>> before, so the thread got a little mixed up.<br>><br>> Yes, I've been trying to reply less on this thread until that sync'ed back<br>> up. :)<br>><br>> From what I've learned in this thread, the (rough) intended C++ model for<br>
> PODs (assuming memory of the right size/alignment) would seem to be "the<br>> lifetime of a B starts when you write to the memory as a B, and ends when<br>> you free the memory or write to the memory as different type." [Disclaimer:<br>
> I'm not sure if "read from the memory as a B" also starts lifetime."]<br>><br>> I think we can do better, but it seems like that's the (rough) intent of the<br>> status quo, leaving aside the question of whether the wording actually says<br>
> that.<br>><br>> *If* that is the (rough) intent, then in:<br>><br>>> void *p = malloc(sizeof(B)); // 1<br>>><br>>> B* pb = (B*)p; // 2<br>>><br>>> pb->i = 0; // 3<br>>><br>
>> short* ps = (short*)p; // 4<br>>> *ps = 0; // 5<br>>><br>>> free(p); // 6<br>><br>><br>> I assume that the reasoning would be that:<br>><br>> line 3 starts the lifetime of a B (we're writing to the bits of a B member,<br>
> not just any int)<br>> line 5 ends the lifetime of that B and begins the lifetime of a short<br>> line 6 ends the lifetime of that short<br>><br>><br>> Again ignoring whether this is desirable, is that (roughly) the intent of<br>
> the current wording?<br>><br>><br>> If yes, does the wording express it (a) accurately and (b) clearly?<br>><br>><br>> Finally, regardless of the above answer, do we want to change anything about<br>
> the legality or semantics of the above type-punning code, such as possibly<br>> having a "type-safe mode" where such code is somehow not allowed unless in<br>> an "extern "C-compat"" block or something?<br>
><br>><br>> Herb<br>><br>><br>><br>> ________________________________<br>> From: <a href="mailto:ub-bounces@open-std.org">ub-bounces@open-std.org</a> <<a href="mailto:ub-bounces@open-std.org">ub-bounces@open-std.org</a>> on behalf of Jeffrey<br>
> Yasskin <<a href="mailto:jyasskin@google.com">jyasskin@google.com</a>><br>> Sent: Friday, January 17, 2014 1:34 PM<br>><br>> To: WG21 UB study group<br>> Subject: Re: [ub] type punning through congruent base class?<br>
> <br>> Note that this post from Herb arrived after<br>> <a href="http://www.open-std.org/pipermail/ub/2014-January/000418.html">http://www.open-std.org/pipermail/ub/2014-January/000418.html</a> but was sent<br>
> before, so the thread got a little mixed up.<br>><br>> On Thu, Jan 16, 2014 at 11:38 AM, Herb Sutter <<a href="mailto:hsutter@microsoft.com">hsutter@microsoft.com</a>> wrote:<br>>><br>>> Richard, it cannot mean that (or if it does, IMO we have an obvious bug)<br>
>> for at least two specific reasons I can think of (below), besides the<br>>> general reasons that it would not be sensical and would violate type safety.<br>><br>><br>> We do have an obvious bug in [basic.life]p1, "The lifetime of an object of<br>
> type T begins when storage with the proper alignment and size for type T is<br>> obtained", if we interpret "obtained" as "obtained from the memory<br>> allocator". Even with strict uses of placement-new to change the type of<br>
> memory, placement-new doesn't "obtain" any memory. If we interpret<br>> "obtained" as just "the programmer intends a region of storage to be<br>> available for a T", as I think Richard is suggesting, the bug is only that<br>
> we need the wording to be clearer.<br>><br>>> First, objects must have unique addresses. Consider, still assuming B is<br>>> trivially constructible:<br>>><br>>> void *p = malloc(sizeof(B));<br>
><br>><br>> The lifetime of a B starts some time after-or-including the malloc() call in<br>> the above line and the access of 'pb->i' two lines down. [basic.life]p5<br>> ("Before the lifetime of an object has started ... The program has undefined<br>
> behavior if ... the pointer is used to access a non-static data member")<br>><br>> The assignment to 'i' might start the lifetime of an 'int' subobject, but<br>> that's not enough to make the use of 'pb->i' defined if no 'B's lifetime has<br>
> started.<br>> <br>>><br>>> B* pb = (B*)p;<br>>> pb->i = 0;<br>><br>><br>> The lifetime of the B *ends* when its storage is re-used for the 'short'<br>> ([basic.life]p1 "The lifetime of an object of type T ends when ... the<br>
> storage which the object occupies is reused"), as Daveed said. This happens<br>> some time after the access in the previous line, and the assignment two<br>> lines down.<br>> <br>>><br>>> short* ps = (short*)p;<br>
>> *ps = 0;<br>>><br>>> This cannot possibly be construed as starting the lifetime of a B object<br>>> and a short object, else they would have the same address, which is illegal.<br>>> Am I missing something?<br>
><br>><br>> Both a B object and a short object have their lifetimes started in your code<br>> snippet, but the lifetimes don't overlap.<br>><br>> Confusingly, the start of these lifetimes is *not* called out in any<br>
> particular line of code; it's implied by them. In particular, the casts<br>> don't have any lifetime effects (contra the straw man at<br>> <a href="http://www.open-std.org/pipermail/ub/2014-January/000406.html">http://www.open-std.org/pipermail/ub/2014-January/000406.html</a>). The code<br>
> would be just as defined (or undefined) written as:<br>><br>> void *p = malloc(sizeof(B));<br>><br>> B* pb = (B*)p;<br>> short* ps = (short*)p;<br>> pb->i = 0;<br>><br>> *ps = 0;<br>
><br>><br>> As Matt alluded to in<br>> <a href="http://www.open-std.org/pipermail/ub/2014-January/000456.html">http://www.open-std.org/pipermail/ub/2014-January/000456.html</a>, it might be<br>> possible to say that all lifetime effects are called out in explicit<br>
> expressions without breaking C compatibility, *if* we instead say that<br>> accessing the members of objects with trivial constructors can be done<br>> outside of the lifetime of such objects. I have no idea whether that would<br>
> be better or worse than saying that lifetime effects can be implied.<br>><br>><br>> Jeffrey<br>><br>><br>> _______________________________________________<br>> ub mailing list<br>> <a href="mailto:ub@isocpp.open-std.org">ub@isocpp.open-std.org</a><br>
> <a href="http://www.open-std.org/mailman/listinfo/ub">http://www.open-std.org/mailman/listinfo/ub</a><br>><br></div>