There seems to be no more silly question than this. But does the standard allow it?
Consider:
void* p = operator new(sizeof(std::string));
*static_cast<std::string*>(p) = "string";
[basic.life]/6:
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated24 or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released... The program has undefined behavior if:
the pointer is used to access a non-static data member or call a non-static member function of the object, or
the pointer is used as the operand of a static_cast ([expr.static.cast]), except when the conversion is to pointer to cv void, or to pointer to cv void and subsequently to pointer to cv char, cv unsigned char, or cv std::byte ([cstddef.syn]), or
(Note that according to [intro.object]/10, a std::string object is not implicitly created by operator new because it is not of implicit-lifetime type.)
However, [basic.life]/6 does not apply to this code because there are no objects at all.
What am I missing?
[intro.object]/10
Some operations are described as implicitly creating objects within a specified region of storage. For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types in its specified region of storage if doing so 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.
[intro.object]/11
Further, after implicitly creating objects within a specified region of storage, some operations are described as producing a pointer to a suitable created object. 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.
[intro.object]/13
Any implicit or explicit invocation of a function named operator new or operator new[] implicitly creates objects in the returned region of storage and returns a pointer to a suitable created object.
If an std::string[1] (or std::string[1][1] etc.) object were created, and a pointer to the std::string subobject were produced by operator new(sizeof(std::string)), then *static_cast<std::string*>(p) = "string" would have undefined behavior per [basic.life]/(7.2)
the glvalue [denoting an out-of-lifetime object] is used to call a non-static member function of the object
If operator new(sizeof(std::string)) produced a pointer to object of some other type (like int or double), then undefined behavior would be triggered by [expr.ref]/8:
If E2 is a non-static member and the result of E1 is an object whose type is not similar to the type of E1, the behavior is undefined.
So, there is no set of objects which would give the program defined behavior. Thus, the highlighted sentence of [intro.object]/10 apply here.
static_cast is fine, however dereferencing resulting pointer to (non-existing) std::string object leads to Undefined Behaviour:
7.2.1 Value category [basic.lval]
11 If a program attempts to access (3.1) the stored value of an object through a glvalue whose type is not similar (7.3.5) to one of the following types the behavior is undefined:
(11.1) the dynamic type of the object,
(11.2) a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
(11.3) a char, unsigned char, or std::byte type.
...
Edit:
Quote from the question is not really applicable here, it refers to a situations like this:
struct foo
{
bar b1;
bar b2;
foo(void): b1{&b2}, b2{} {}
};
The behaviour is undefined by omission.
The assignment operator is overloaded, so a member function named operator= is invoked. The standard says
A non-static member function may be called for an object of its class type
There isn't any object of an appropriate type, and there is nothing else in the standard that might be giving meaning to this program.
Is it legal to access a non-existent object?
No, you wrote it yourself:
The program has undefined behavior if:
the pointer is used to access a non-static data member or call a non-static member function of the object
which is exactly what *static_cast<std::string*>(p) = "string"; does. You must construct a std::string before you call the assignment operator:
int main() {
void* p = operator new(sizeof(std::string));
std::string* sp = new(p) std::string; // construct the string
*sp = "string"; // now assignment is fine
sp->~basic_string();
operator delete(p);
}
However, [basic.life]/6 does not apply to this code because there are no objects at all.
Yes it's applicable here. Your code has allocated the storage but not started the lifetime of the object and calls a non-static member function of the (non-existing) object.
The confusion seems to stem from the fact that you never start the lifetime of the object and you therefore think that "before the lifetime" doesn't apply. It does. It's "before the lifetime" until you start the lifetime. If you never start the lifetime of the object, it's "before the timetime" during the complete program run.
The standard clause could have said: The program has undefined behavior if the pointer is used to access a non-static data member or call a non-static member function of the object if the object's lifetime has not been started - and it would mean the same thing.
Related
The following code example is from cppreference on std::launder:
alignas(Y) std::byte s[sizeof(Y)];
Y* q = new(&s) Y{2};
const int f = reinterpret_cast<Y*>(&s)->z; // Class member access is undefined behavior
It seems to me that third line will result in undefined behaviour because of [basic.life]/6 in the standard:
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated ... The program has undefined behavior if ... the pointer is used to access a non-static data member or call a non-static member function of the object.
Why didn't placement new start the lifetime of object Y?
The placement-new did start the lifetime of the Y object (and its subobjects).
But object lifetime is not what std::launder is about. std::launder can't be used to start the lifetime of objects.
std::launder is used when you have a pointer which points to an object of a type different than the pointer's type, which happens when you reinterpret_cast a pointer to a type for which there doesn't exist an object of the target type which is pointer-interconvertible with the former object.
std::launder can then (assuming its preconditions are met) be used to obtain a pointer to an object of the pointer's type (which must already be in its lifetime) located at the address to which the pointer refers.
Here &s is a pointer pointing to an array of sizeof(Y) std::bytes. There is also an explicitly created Y object sharing the address with that array and the array provides storage for the Y object. However, an array (or array element) is not pointer-interconvertible with an object for which it provides storage. Therefore the result of reinterpret_cast<Y*>(&s) will not point to the Y object, but will remain pointing to the array.
Accessing a member has undefined behavior if the glvalue used doesn't actually refer to an object (similar) to the glvalue's type, which is here the case as the lvalue refers to the array, not the Y object.
So, to get a pointer and lvalue to the Y object located at the same address as &s and already in its lifetime, you need to call std::launder first:
const int f = std::launder(reinterpret_cast<Y*>(&s))->z;
All of this complication can of course be avoided by just using the pointer returned by new directly. It already points to the newly-created object:
const int f = q->z;
int main(){
auto* ptr = (int*) ::operator new(sizeof(int)*10, std::align_val_t(alignof(int))); //#1
ptr[1] = 4; //#a
}
Consider the above code, what the standard says are listed in the following:
basic.stc.dynamic.allocation
The pointer returned shall be suitably aligned so that it can be converted to a pointer to any suitable complete object type ([new.delete.single]) and then used to access the object or array in the storage allocated
expr.new#8
If the allocated type is a non-array type, the allocation function's name is operator new and the deallocation function's name is operator delete. If the allocated type is an array type, the allocation function's name is operator new[] and the deallocation function's name is operator delete[].
expr.new#1
If the entity is a non-array object, the new-expression returns a pointer to the object created. If it is an array, the new-expression returns a pointer to the initial element of the array
And the rules about pointer arithmatic says:
expr.add#4
If the expression P points to element x[i] of an array object x with n elements,86 the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i+j] if 0≤ i+j ≤ n; otherwise, the behavior is undefined.
So, I wonder Is it an undefined behavior when use the pointer at #a? I think it violate the bullet 4. In addition, at the look of implementation of std::allocate of MSVC. It seems to use operator new() to allocate the space and use the return pointer as a pointer to element of array.
It seems to the standard does not say what the return pointer point to what original object when directly invoke ::operator new(...). It only says the return pointer that resulted from invoking such allocate function can be converted to a pointer to an object which has suitably aligned.
UPDATE:
what I concerned is dynamic-construction-of-arrays
The most implementation of std::vector use the std::allocate and std::vector has a non-static data member record the result from std::allocate. When use the object of std::vector as arr[i], the implementation will use the non-static data member as the pointer to element of array type to access arr[i]. I think it should be UB? I.E, we are permitted to use the pointer that return from allocation function as the operand of new-placement to construct an object, However If we use the pointer to access ith object or any iterator to access ith object, It means it's UB?
The expression:
::operator new(sizeof(int)*10, std::align_val_t(alignof(int)));
is a function call expression to the global allocation function. It is not using the new expression to allocate storage and construct an object or array of objects. The global allocator functions only return raw storage and do not construct objects in the memory allocated.
Inside basic.stc.dynamic.allocation
The pointer returned shall be suitably aligned so that it can be converted to a pointer to any suitable complete object type ([new.delete.single]) and then used to access the object or array in the storage allocated [...]
the object is an object that is not implicitly created. It is supposed to be created according to [intro.object]/1 in the code.
So, in this condition, you know that the expression ptr[1] has conceptualy 2 undefined behavior:
ptr+1 is undefined behavior because ptr value is not a pointer to array expr.add
*(ptr+1) is undefined behavior because the value of ptr is not pointer to object [expr.unary.op]/1
According to c++20 this code has a well defined behavior. Because an implicitly created array object of type int[N] with N>1 with its elements also implicitly created would give this code defined behavior.
[intro.object]/13
Any implicit or explicit invocation of a function named operator new or operator new[] implicitly creates objects in the returned region of storage and returns a pointer to a suitable created object.
[intro.object]/10
Some operations are described as implicitly creating objects within a specified region of storage. For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types ([basic.types]) in its specified region of storage if doing so 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. If multiple such sets of objects would give the program defined behavior, it is unspecified which such set of objects is created.
These two paragraphes are a kind of revolution in the way the language is specified:
What happen is not determined, rather it is a possible set implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types
the constraint does not depend on a given point in the code or in the program execution, rather it depends on the entire program execution: if doing so would result in the program having defined behavior.
So code validity depends on an induction, this is what I think is a revolution. For example in the case abose, the reasoning would be: let's suppose that the allocation function call would have returned a pointer to an object of type int [1], so the code as defined behavior, so the assumption is correct
But this implicit object remain hypothetical until the entire program has executed. For exemple if somewhere else in the code an int is created at ptr[2] the reasoning could be changed to:let's suppose that the allocation function call would have returned a pointer to an object of type int [2], so the code as defined behavior, so the assumption is correct
Allow me to preface by saying that I don't recommend any of the practices below, for obvious reasons. However, I had a discussion today regarding it and some people were adamant about using a reference like this as being undefined behavior.
Here is a test case:
#include <string>
struct my_object {
int a = 1;
int b = 2;
std::string hi = "hello";
};
// Using union purely to reserve uninitialized memory for a class.
union my_object_storage {
char dummy;
my_object memory;
// C++ will yell at you for doing this without some constructors.
my_object_storage() {}
~my_object_storage() {}
} my_object_storage_instance;
// This is so we can easily access the storage memory through "I"
constexpr my_object &I = my_object_storage_instance.memory;
//-------------------------------------------------------------
int main() {
// Initialize the object.
new (&I) my_object();
// Use the reference.
I.a = 1;
// Destroy the object (typically this should be done using RAII).
I.~my_object();
// Phase two, REINITIALIZE an object with the SAME reference.
// We still have the memory allocated which is static, so why not?
new (&I) my_object();
// Use the reference.
I.a = 1;
// Destroy the object again.
I.~my_object();
}
https://wandbox.org/permlink/YEp9aQUcWdA9YiBI
Basically what the code does is reserves static memory for a struct, and then initializes it in main(). Why would you want to do that? It isn't extremely useful and you should just use a pointer, but here is the question:
With this statement given,
constexpr my_object &I = my_object_storage_instance.memory;
is defining a reference to uninitialized memory undefined behavior? Other people have told me it is, but I'm trying to figure out concretely if that's the case. In the C++ standard we see this paragraph:
A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior.
Specifically "a valid object", which may boil down to: is an object that hasn't had its constructor called yet "valid"? What makes it invalid that it would cause undefined behavior? Are there actually real side effects that could arise?
My argument for this being labeled as undefined behavior is:
Compilers might be free to treat it like a valid object, because the standard states that it should be, especially during the assignment and especially if there are hidden debug instructions being inserted for diagnostics that assume such, which would certainly cause undefined behavior.
My arguments against it being undefined behavior is that:
It's not dereferencing anything - the paragraph states that, during initialization of a reference, dereferencing nullptr is undefined. It doesn't specifically state undefined behavior if there isn't any dereferencing.
Dangling references are a thing, and appear in many cases in normal programs. They only cause undefined behavior IF they are used. This is similar to starting with a dangling reference.
Again, not very useful in practice because there are much better ways to spend your time, but what better place for odd questions and expert opinions than stackoverflow? :)
You're perfectly fine, your usage of the reference falls into the explicit exception to the rule that a live object is required. In [basic.life]:
Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways.
For an object under construction or destruction, see [class.cdtor]. Otherwise, such a glvalue refers to allocated storage ([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 if:
the glvalue is used to access the object, or
the glvalue is used to call a non-static member function of the object, or
the glvalue is bound to a reference to a virtual base class ([dcl.init.ref]), or
the glvalue is used as the operand of a dynamic_cast ([expr.dynamic.cast]) or as the operand of typeid.
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
the storage for the new object exactly overlays the storage location which the original object occupied, and
the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
neither the original object nor the new object is a potentially-overlapping subobject ([intro.object]).
Thus, your reference validly refers to allocated storage, which is exactly what you need to perform a placement-new and vivify the union member.
And since the dynamic (runtime) type of the object you create exactly matches the static type of the reference you hold, it can be used to access the new object after placement new (either the first or the second).
This is almost standard textbook use of placement new
template<size_t Len, size_t Align>
class aligned_memory
{
public:
aligned_memory() : data((char*)(((std::uintptr_t)mem + Align - 1) & -Align)) {}
char* get() const {return data;}
private:
char mem[Len + Align - 1];
char* data;
};
template<typename T, size_t N>
class Array
{
public:
Array() : sz(0) {}
void push_back(const T& t)
{
new (data.get() + sz++ * sizeof(T)) T(t);
}
void pop_back()
{
((T*)data.get() + --sz)->~T();
}
private:
aligned_memory<N * sizeof(T), alignof(T)> data;
size_t sz;
};
Seems pretty fine, until we look into strict-aliasing, there seems to be some conflict in whether this is well-formed
Camp ill-formed
C++'s Strict Aliasing Rule - Is the 'char' aliasing exemption a 2-way street?
Strict aliasing rule and 'char *' pointers
Camp well-formed
Does encapsulated char array used as object breaks strict aliasing rule
How to avoid strict aliasing errors when using aligned_storage
They all agree on char* may always reference another object, but some point out its ill-formed to do so the other way round.
Clearly our char[] converts to char* then casted to T*, with which it is used to call its destructor.
So, does the above program break the strict-aliasing rule? Specifically, where in the standard does it says it is well-formed or ill-formed?
EDIT: as background info, this is written for C++0x, before the advent of alignas and std::launder. Not asking specifically for a C++0x solution, but it is preferred.
alignof is cheating, but its here for example purposes.
Gathering from the hints throughout the countless helpful comments, here is my interpretation of what's happening.
TLDR its well-formed‡see edit
Quoting in the order I find more logical from [basic.life]†
The properties ascribed to objects and references throughout this International Standard apply for a given object or reference only during its lifetime.
An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than a trivial default constructor. [...] The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
if the object has non-vacuous initialization, its initialization is complete.
The lifetime of an object o of type T ends when:
if T is a class type with a non-trivial destructor , the destructor call starts, or
the storage which the object occupies is released, or is reused by an object that is not nested within o
From [basic.lval]†
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
a char, unsigned char, or std::byte type.
We deduce that
The lifetime of the chars in the char[] ends when another object reuses that space.
The lifetime of an object of type T started when push_back is called.
Since the address ((T*)data.get() + --sz) is always that of an object with type T whose lifetime has started and not yet ended, it is valid to call ~T() with it.
During this process, the char[] and char* in aligned_memory aliases objects of type T but it is legal to do so. Also, no glvalue is obtained from them, so they could have been pointers of any type.
To answer my own question in the comments whether using any memory as storage is also well-formed
U u;
u->~U();
new (&u) T;
((T*)&u)->~T();
new (&u) U;
Following the 4 points above, the answer is yes‡see edit, as long as the alignment of U is not weaker than T.
‡ EDIT: I've neglected another paragraph of [basic.life]
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
the storage for the new object exactly overlays the storage location which the original object occupied, and
the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
the original object was a most derived object of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).
Which means even though using the object is well-formed, the means which the object is obtained is not. Specifically, post C++17, std::launder has to be called
(std::launder((T*)data.get()) + --sz)->~T();
Prior C++17, a workaround would be to use the pointer acquired from the placement new instead
T* p = new (data.get() + sz++ * sizeof(T)) T(t); // store p somewhere
† Quoted from n4659, as far as I can see, same holds for n1905
Placement-new creates an object at the specified location (C++14 expr.new/1), and ends the lifetime of any other object that was occupying the location (basic.life/1.4).
The code ((T*)data.get() + --sz)->~T(); accesses an object of type T at the location where there is an object of type T. This is fine. It is irrelevant if there used to be a char array at the location.
I use MS Visual Studio 2010.
I made implementation of a double-linked list.
I wonder why in main function after invoking method Clean, which invoke destructor of an object, after I refer to the object no errors are raised.
Here are some of my double-linked list methods(relative to my question):
/*DoubleLinkedList.cpp */
DoubleLinkedList::~DoubleLinkedList(void)
{
cout << "Destructor invoked" << endl;
// as for data nodes memory is allocated in heap we have to release it:
const Node* const_iterator = m_head.m_next;
while (const_iterator != &m_tail)
{
const_iterator = const_iterator->m_next;
delete const_iterator->m_prev;
}
}
void DoubleLinkedList::Clean(void)
{
cout << "Clean invoked" << endl;
this->~DoubleLinkedList(); /* According to C++ 11 standart: Once a destructor is invoked for an object, the object no longer exists*/
}
/* main.cpp */
int main(int argc, char* argv[])
{
DoubleLinkedList list;
Circle c1, c2(MyPoint(1,1),50), c3(MyPoint(2,2),30);
list.Front(&c1);
list.Front(&c2);
list.Front(&c3);
list.Show();
list.Sort();
list.Show();
list.Clean();
list.Show(); /* Recall how Clean method is implemented. As list no longer exist, run-time error is expected here, but flow of executon continues and Show, Push_back preforms fine*/
list.Push_back(&c1);
list.Push_back(&c2);
list.Push_back(&c3);
Question: *As stated in the 11 standart of C++ after destructor is called - object no longer exists*, why I am still able to use the object after it`s destructor was invoked?
The important thing to consider here is the lifetime of the object. The lifetime of an object must exist within the time that the storage for that object has been allocated. Many objects can exist at a single storage location within the time that storage is allocated, one after the other.
Normally, when an object is destroyed (through going out of scope or calling delete), the objects lifetime is ended and then its memory is deallocated. However, when you call the destructor explicitly, all you do is end the lifetime of your object. The standard has no definition of the object "not existing", but it does have the concept of its lifetime ending.
The lifetime of an object of type T ends when:
if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
the storage which the object occupies is reused or released.
Now, in the state after an objects lifetime has ended and before its storage has been deallocated, there are only very specific things you can do. The standard defines things that can be done in this state to both pointers and glvalues. In your case, list is a glvalue, so we'll take a look at the rules for that:
[...] after the lifetime of an object has ended and before the storage which the object
occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. [...] The program has undefined behavior if:
an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,
the glvalue is used to access a non-static data member or call a non-static member function of the object, or
the glvalue is implicitly converted (4.10) to a reference to a base class type, or
the glvalue is used as the operand of a static_cast (5.2.9) except when the conversion is ultimately to cv char& or cv unsigned char&, or
the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid.
The second list item applies here. You have undefined behaviour beacuse you're accessing a non-static member function after the objects lifetime has ended.
Use object AFTER it's destroyed (i.e. destructor's is called) is UB. UB means undefined behaviour. So...