sololearn solo para learn c++ arrays initialization dynamic-memory-allocation placement-new

c++ - sololearn - solo learn para pc



Operador de mezcla nuevo[] y colocación nueva con eliminación ordinaria (5)

Correcto sería:

X* p = static_cast<X*>(new char[3 * sizeof(X)]); // ... delete[] static_cast<char*>(p);

o

X* p = static_cast<X*>(operator new[](3 * sizeof(X))); // ... operator delete[](p);

El tipo de expresión de eliminación de matriz tiene que coincidir exactamente con la nueva expresión.

El primer ejemplo es UB porque la sección 5.3.5 ( [expr.delete] ) dice

En la primera alternativa ( eliminar objeto ), si el tipo estático del objeto que se eliminará es diferente de su tipo dinámico, el tipo estático será una clase base del tipo dinámico del objeto que se eliminará y el tipo estático tendrá Un destructor virtual o el comportamiento es indefinido. En la segunda alternativa ( eliminar matriz ) si el tipo dinámico del objeto a eliminar difiere de su tipo estático, el comportamiento es indefinido.

Mi versión corregida está bien porque (sección 3.9 [basic.life] ):

Un programa puede terminar la vida útil de cualquier objeto reutilizando el almacenamiento que el objeto ocupa o llamando explícitamente al destructor para un objeto de un tipo de clase con un destructor no trivial. Para un objeto de un tipo de clase con un destructor no trivial, no se requiere que el programa llame al destructor explícitamente antes de que el almacenamiento que ocupa el objeto se reutilice o libere; sin embargo, si no hay una llamada explícita al destructor o si no se usa una expresión de eliminación (5.3.5) para liberar el almacenamiento, el destructor no se llamará implícitamente y cualquier programa que dependa de los efectos secundarios producidos por el destructor tiene un comportamiento no definido.

El segundo ejemplo no está permitido si X tiene un destructor no trivial porque (también 3.9 [basic.life] ):

Antes de que la vida útil de un objeto haya comenzado, pero después de que el almacenamiento que el objeto ocupará se haya asignado 38 o, una vez que haya finalizado la vida útil de un objeto y antes de que el almacenamiento que ocupa el objeto sea reutilizado o liberado, cualquier puntero que haga referencia al la ubicación de almacenamiento donde se ubicará o se ubicará el objeto se puede usar, pero solo de manera limitada. Para un objeto en construcción o destrucción, ver 12.7. De lo contrario, dicho puntero se refiere al almacenamiento asignado (3.7.4.2), y el uso del puntero como si el puntero fuera de tipo void* , está bien de fi nido. Dicho puntero puede ser referenciado pero el valor de l resultante solo se puede usar de manera limitada, como se describe a continuación.

El programa tiene un comportamiento indefinido si:

  • el objeto será o fue de un tipo de clase con un destructor no trivial y el puntero se utiliza como el operando de una expresión de eliminación,

Solo por curiosidad, ¿es legal lo siguiente?

X* p = static_cast<X*>(operator new[](3 * sizeof(X))); new(p + 0) X(); new(p + 1) X(); new(p + 2) X(); delete[] p; // Am I allowed to use delete[] here? Or is it undefined behavior?

Similar:

X* q = new X[3](); (q + 2)->~X(); (q + 1)->~X(); (q + 0)->~X(); operator delete[](q);


Creo que eso no puede ser legal. Porque eso implica estas ecuaciones:

new-expression = allocation-function + constructor delete-expression = destructor + deallocation-function

Nada más y nada menos. Pero la Norma no dice exactamente eso, que yo sepa. Es posible que la new-expression haga más que allocation-function + constructor de allocation-function + constructor juntos. Es decir, las ecuaciones reales podrían ser esto, y el Estándar no lo prohíbe explícitamente en ninguna parte:

new-expression = allocation-function + constructor + some-other-work delete-expression = destructor + deallocation-function + some-other-work


Desde 5.3.5 [expr.delete] en n3242:

2

[...]

En la segunda alternativa ( eliminar matriz ), el valor del operando de eliminar puede ser un valor de puntero nulo o un valor de puntero que resultó de una nueva expresión de matriz anterior. Si no, el comportamiento es indefinido. [...]

lo que significa que para delete[] p , p debe haber sido el resultado de algo de la forma new[] p (una nueva expresión), o 0. Al ver el resultado del operator new no aparece aquí, creo que el primer caso es justo fuera

Creo que el segundo caso está bien. Desde 18.6.1.2 [new.delete.array]:

11

void operator delete[](void* ptr) noexcept;

[...]

Requiere: ptr será un puntero nulo o su valor será el valor devuelto por una llamada anterior al operador new o al operador new [] (std :: size_t, const std :: nothrow_t &) que no haya sido invalidado por una llamada intermedia a operador eliminar [...]

(hay un texto similar en 3.7.4.2 [basic.stc.dynamic.deallocation], párrafo 3)

Entonces, siempre que las funciones de / asignación coincidan (p. Ej., delete[] (new[3] T) está bien formada) no ocurre nada malo. [o lo hace? vea abajo ]

Creo que rastreé el texto normativo de lo que Jerry está advirtiendo, en 5.3.4 [expr.new]:

10

Una nueva expresión pasa la cantidad de espacio solicitado a la función de asignación como el primer argumento de tipo std :: size_t. Ese argumento no será menor que el tamaño del objeto que se está creando; puede ser mayor que el tamaño del objeto que se crea solo si el objeto es una matriz. [...]

A continuación, en el mismo párrafo hay un ejemplo (por lo tanto, no normativo) que subraya que las nuevas expresiones de una implementación pueden solicitar más a la función de asignación que al espacio que ocupa la matriz (almacenar el parámetro opcional std::size_t disponible para La función de desasignación viene a la mente), y que pueden compensar el resultado. Así que todas las apuestas están apagadas en el caso de la matriz. El caso no-matriz parece bien sin embargo:

auto* p = new T; // Still icky p->~T(); operator delete(p);


Estoy bastante seguro de que ambos dan UB.

§5.3.4 / 12 dice que la forma de matriz de una nueva expresión puede agregar una cantidad arbitraria de sobrecarga a la cantidad de memoria asignada. La eliminación de matriz puede / podría hacer algo con la memoria adicional que espera que esté allí, pero no es así porque no asignó el espacio adicional que espera. Como mínimo, normalmente compensará al menos la cantidad de memoria adicional que se espera que se asigne para volver a la dirección que cree que le devolvió el operator new , pero como no ha asignado memoria adicional ni ha aplicado un desplazamiento , cuando lo haga, pasará un puntero al operator delete[] que no fue devuelto por el operator new[] , lo que lleva a UB (y, de hecho, incluso intentar formar la dirección antes del comienzo de la dirección devuelta es técnicamente UB).

La misma sección dice que si asigna memoria adicional, tiene que compensar el puntero devuelto por la cantidad de esa sobrecarga. Cuando / si llama al operator delete[] con el puntero que se devolvió de la nueva expresión sin compensar el desplazamiento, está llamando al operator delete[] con un puntero que es diferente del operator new[] devuelto, dando UB nuevamente .

§5.3.4 / 12 es una nota no normativa, pero no veo nada en el texto normativo que la contradiga.


Si no son UB, deberían serlo. En el ejemplo 1, está utilizando delete[] donde el mecanismo subyacente no tiene idea de cuántos objetos se destruirán. Si la implementación de new[] y delete[] utiliza cookies, esto fallará. El código en el ejemplo 2 asume que la dirección q es la dirección correcta para pasar al operator delete[] , y este no es el caso en una implementación que usa cookies.