home bbs files messages ]

Forums before death by AOL, social media and spammers... "We can't have nice things"

   comp.lang.c++.moderated      Moderated discussion of C++ superhackery      33,346 messages   

[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]

   Message 32,275 of 33,346   
   Johannes Schaub to All   
   Re: Trivial initialization after non-tri   
   10 May 12 16:18:18   
   
   e6174bcb   
   From: schaub.johannes@googlemail.com   
      
   Am 10.05.2012 20:48, schrieb Nikolay Ivchenkov:   
   > Consider the following example:   
   >   
   >     struct X   
   >     {   
   >         ~X() {}   
   >     };   
   >   
   >     template   
   >         void destroy(T&x)   
   >             { x.~T(); }   
   >   
   >     int main()   
   >     {   
   >         X *p = (X *)operator new(sizeof(X));   
   >         destroy(*p);   
   >         destroy(*p); // well-defined or undefined?   
   >         operator delete(p);   
   >     }   
   >   
   > According to C++11 - 3.8/1, non-trivial destruction ends the life-time   
   > of an object. Can we assume that a new object of the same type exists   
   > at the same location immediately after such non-trivial destruction   
   > has done if its initialization is trivial?   
   >   
      
   According to 3.8/1, there exist all objects of sizeof(X) with suitable   
   alignment at that object that have trivial initialization :)   
      
   Of course that's not how it really is, so 3.8/1 is broken as it is. We   
   cannot derive a meaningful statement. So let me allow a more elaborative   
   explanation of my view of your code.   
      
   Issue 1116 tries to solve this, so that in your code, also the first   
   "destroy" is undefined behavior because you have not yet copied another   
   T object into "*p". Unfortunately this isn't too easy to do with   
   classes, because a simple "*p = X();" will invoke a member function on   
   "*p" before even having the "X" object created at "*p". I guess you   
   would have to memcpy the "X" into "p", something like (i hope I have the   
   parameter order right).   
      
       memcpy(p, &(X const&)X(), sizeof *p);   
      
      
   I don't think that is acceptable for most users, though.   
      
   I lately came to the following conclusions (they are by no means   
   "normative". All of this is by the necessity of the spec being way too   
   unspecific IMO):   
      
   - The start of lifetime of an object of trivial initialization is the   
   same as the start of existence of that object (it may be "out of   
   lifetime". It is during its ctor run, and before it. In the latter case,   
   it's almost unusable except for the non-value uses).   
      
   - The start of lifetime of other objects equals the start of lifetime of   
   them. The existence of the object is implied by its start of lifetime.   
   It's the "created by the implementation when needed" case of 1.8p1,   
   despite the "weird" cross reference :)   
      
   - The end of lifetime of a class object with a non-trivial dtor may be   
   different from the end of its existence (in particular, during the dtor   
   run, the object is out of lifetime but still existent).   
      
   - For other objects, the end of lifetime means the stop of existence of   
   the object, except for objects that were created by a definition,   
   new-expression or as a temporary (cases where "storage is allocated for   
   an object of type T"). These objects remain existent but out-of-lifetime.   
      
   - Reusing the storage of any object stops the lifetime of the object and   
   may cause the end of its existence according to the rules above.   
      
   Also:   
      
   - Certain rules in C++ that refer to an object's existence seem to be   
   better interpretable when understood to refer to an object's alive   
   state. For example, 3.8p8.   
      
   - A member access expression denotes an "access" by an lvalue of the   
   object expression too, aswell as by an lvalue of the member. If we   
   do a write access by an lvalue of type X, we start lifetime of an object   
   of type X if X has trivial initialization.   
      
   So I think the following has undefined behavior since we have an   
   aliasing violation   
      
       struct A { int a; };   
       struct B { int b; };   
      
       A *a = (A*) malloc(sizeof *a);   
       a->a = 10;   
         // now an "A" and an "int" object are alive   
      
       B *b = (B*)a;   
       b->a = 0;   
         // the now a "B" and an "int" object are alive   
         // (we reused storage)   
      
       int x = a->a;   
         // alias violation: Access by lvalue of type "A"   
         // to object of type "B".   
      
      
   To come to your code, I think it has undefined behavior, because it   
   calls a member function (destructor) on an object expression of type   
   "X", but there is no object of type "X" in existence (see 3.8p5).   
      
   You can create one by writing into a data member of "X" or by doing a   
   "placement-new" of X into that location. The write of the data member   
   starts the lifetime of the "X" object, which in turn allows the access   
   of the non-static data member.   
      
      
   --   
         [ See http://www.gotw.ca/resources/clcm.htm for info about ]   
         [ comp.lang.c++.moderated.    First time posters: Do this! ]   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   

[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]


(c) 1994,  bbs@darkrealms.ca