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;
Related
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.
In order to stem the argument going on in the comments of an answer I gave recently, I'd like some constructive answers to the following questions:
Is a reference's lifetime distinct from the object it refers to? Is a reference simply an alias for its target?
Can a reference outlive its target in a well-formed program without resulting in undefined behaviour?
Can a reference be made to refer to a new object if the storage allocated for the original object is reused?
Does the following code demonstrate the above points without invoking undefined behaviour?
Example code by Ben Voigt and simplified (run it on ideone.com):
#include <iostream>
#include <new>
struct something
{
int i;
};
int main(void)
{
char buffer[sizeof (something) + 40];
something* p = new (buffer) something;
p->i = 11;
int& outlives = p->i;
std::cout << outlives << "\n";
p->~something(); // p->i dies with its parent object
new (p) char[40]; // memory is reused, lifetime of *p (and p->i) is so done
new (&outlives) int(13);
std::cout << outlives << "\n"; // but reference is still alive and well
// and useful, because strict aliasing was respected
}
Is a reference's lifetime distinct from the object it refers to? Is a reference simply an alias for its target?
A reference has its own lifetime:
int x = 0;
{
int& r = x;
} // r dies now
x = 5; // x is still alive
A ref-to-const additionally may extend the lifetime of its referee:
int foo() { return 0; }
const int& r = foo(); // note, this is *not* a reference to a local variable
cout << r; // valid; the lifetime of the result of foo() is extended
though this is not without caveats:
A reference to const only extends the lifetime of a temporary object if the reference is a) local and b) bound to a prvalue whose evaluation creates said temporary object. (So it doesn't work for members, or local references which are bound to xvalues.) Also, non-const rvalue references extend the lifetime in the exact same fashion. [#FredOverflow]
Can a reference outlive its target in a well-formed program without resulting in undefined behaviour?
Sure, as long as you don't use it.
Can a reference be made to refer to a new object if the storage allocated for the original object is reused?
Yes, under some conditions:
[C++11: 3.8/7]: 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 (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).
Does the following code demonstrate the above points without invoking undefined behaviour?
Tl;dr.
Yes. For example local nonstatic references have automatic storage duration and corresponding liifetime and can refer to objects that have longer lifetime.
Yes, dangling references are an example. As long as such references are not used in any expressions when they become dangling, they are fine.
There is a special rule in clause 3 about this case. Names of objects, pointers and references automatically refer to the new object that reuses the storage under restricted conditions. I believe it is at the end of 3.8. Someone who has the spec handy please fill in the correct ref here.
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).
In order to stem the argument going on in the comments of an answer I gave recently, I'd like some constructive answers to the following questions:
Is a reference's lifetime distinct from the object it refers to? Is a reference simply an alias for its target?
Can a reference outlive its target in a well-formed program without resulting in undefined behaviour?
Can a reference be made to refer to a new object if the storage allocated for the original object is reused?
Does the following code demonstrate the above points without invoking undefined behaviour?
Example code by Ben Voigt and simplified (run it on ideone.com):
#include <iostream>
#include <new>
struct something
{
int i;
};
int main(void)
{
char buffer[sizeof (something) + 40];
something* p = new (buffer) something;
p->i = 11;
int& outlives = p->i;
std::cout << outlives << "\n";
p->~something(); // p->i dies with its parent object
new (p) char[40]; // memory is reused, lifetime of *p (and p->i) is so done
new (&outlives) int(13);
std::cout << outlives << "\n"; // but reference is still alive and well
// and useful, because strict aliasing was respected
}
Is a reference's lifetime distinct from the object it refers to? Is a reference simply an alias for its target?
A reference has its own lifetime:
int x = 0;
{
int& r = x;
} // r dies now
x = 5; // x is still alive
A ref-to-const additionally may extend the lifetime of its referee:
int foo() { return 0; }
const int& r = foo(); // note, this is *not* a reference to a local variable
cout << r; // valid; the lifetime of the result of foo() is extended
though this is not without caveats:
A reference to const only extends the lifetime of a temporary object if the reference is a) local and b) bound to a prvalue whose evaluation creates said temporary object. (So it doesn't work for members, or local references which are bound to xvalues.) Also, non-const rvalue references extend the lifetime in the exact same fashion. [#FredOverflow]
Can a reference outlive its target in a well-formed program without resulting in undefined behaviour?
Sure, as long as you don't use it.
Can a reference be made to refer to a new object if the storage allocated for the original object is reused?
Yes, under some conditions:
[C++11: 3.8/7]: 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 (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).
Does the following code demonstrate the above points without invoking undefined behaviour?
Tl;dr.
Yes. For example local nonstatic references have automatic storage duration and corresponding liifetime and can refer to objects that have longer lifetime.
Yes, dangling references are an example. As long as such references are not used in any expressions when they become dangling, they are fine.
There is a special rule in clause 3 about this case. Names of objects, pointers and references automatically refer to the new object that reuses the storage under restricted conditions. I believe it is at the end of 3.8. Someone who has the spec handy please fill in the correct ref here.