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.
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:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With