Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it an undefined behavior when use the pointer that return from operator new as a pointer to element of array

Tags:

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

  1. 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

  1. 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

  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

  1. 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?

like image 368
xmh0511 Avatar asked Jun 30 '20 07:06

xmh0511


1 Answers

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

  1. 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

like image 75
Oliv Avatar answered Oct 02 '22 14:10

Oliv