vectores tipos punteros puntero matrices funciones ejemplo declaracion con aritmetica c++ micro-optimization placement-new noexcept

c++ - tipos - Pasar el puntero nulo a la ubicación nueva



punteros y vectores en c (1)

El new operador de ubicación predeterminado se declara en 18.6 [support.dynamic] ¶1 con una especificación de excepción que no arroja:

void* operator new (std::size_t size, void* ptr) noexcept;

Esta función no hace más que return ptr; por lo que es razonable que sea noexcept , sin embargo de acuerdo con 5.3.4 [expr.new] ¶15 esto significa que el compilador debe verificar que no devuelve null antes de invocar el constructor del objeto:

-15-
[ Nota: a menos que una función de asignación se declare con una std::bad_alloc excepción que no std::bad_alloc (15.4), indica una falla al asignar almacenamiento lanzando una excepción std::bad_alloc (Cláusula 15, 18.6.2.1); devuelve un puntero no nulo en caso contrario. Si la función de asignación se declara con una especificación de excepción que no arroja, devuelve nulo para indicar que no se ha asignado almacenamiento y un puntero no nulo en caso contrario. -Finalizar ] Si la función de asignación devuelve nulo, no se realizará la inicialización, no se invocará la función de desasignación, y el valor de la nueva expresión será nulo.

Me parece que (específicamente para la colocación new , no en general) esta comprobación nula es un golpe de rendimiento desafortunado, aunque pequeño.

He estado depurando algún código donde la ubicación new se estaba utilizando en una ruta de código muy sensible al rendimiento para mejorar la generación de código del compilador y se observó la comprobación de nulo en el ensamblado. Al proporcionar una new sobrecarga de colocación específica de clase que se declara con una especificación de excepción lanzada (aunque posiblemente no se pueda lanzar) se eliminó la rama condicional, lo que también permitió al compilador generar código más pequeño para las funciones en línea circundantes. El resultado de decir que la new función de ubicación podría arrojar, aunque no pudo , fue un código mensurablemente mejor.

Así que me he estado preguntando si realmente se requiere el cheque nulo para el new caso de colocación. La única forma en que puede devolver nulo es si lo pasa nulo. Aunque es posible, y aparentemente legal, escribir:

void* ptr = nullptr; Obj* obj = new (ptr) Obj(); assert( obj == nullptr );

No veo por qué sería útil, sugiero que sería mejor si el programador tuviera que verificar nulo explícitamente antes de usar la ubicación new por ejemplo,

Obj* obj = ptr ? new (ptr) Obj() : nullptr;

¿Alguna vez alguien necesitó una ubicación new para manejar correctamente la caja del puntero nulo? (es decir, sin agregar una verificación explícita de que ptr es una ubicación de memoria válida).

Me pregunto si sería razonable prohibir pasar un puntero nulo a la new función de ubicación predeterminada, y si no hay una forma mejor de evitar la rama innecesaria, aparte de intentar decirle al compilador que el valor no es nulo, por ejemplo

void* ptr = getAddress(); (void) *(Obj*)ptr; // inform the optimiser that dereferencing pointer is valid Obj* obj = new (ptr) Obj();

O:

void* ptr = getAddress(); if (!ptr) __builtin_unreachable(); // same, but not portable Obj* obj = new (ptr) Obj();

NB: esta pregunta es una microoptimización etiquetada intencionalmente, no estoy sugiriendo que sobrecargues la ubicación new para todos tus tipos para "mejorar" el rendimiento. Este efecto se notó en un caso muy específico de rendimiento crítico y se basó en el perfil y la medición.

Actualización: DR 1748 hace que el comportamiento indefinido sea utilizar un puntero nulo con la ubicación nueva, por lo que los compiladores ya no están obligados a realizar el control.


Aunque no puedo ver una gran cantidad de preguntas allí, excepto "¿Alguien ha necesitado una ubicación nueva para manejar correctamente el caso del puntero nulo?" (No lo hice), creo que el caso es lo suficientemente interesante como para derramar algunas ideas sobre el tema.

Considero el estándar roto o incompleto con la nueva función de ubicación y los requisitos para las funciones de asignación en general.

Si observa detenidamente el §5.3.4,13 citado, implica que cada función de asignación debe verificarse para un nullpointer devuelto, incluso si no es noexcept . Por lo tanto, debe ser reescrito para

Si la función de asignación se declara con una especificación de excepción que no arroja y devuelve nulo, no se realizará la inicialización, no se invocará la función de desasignación y el valor de la nueva expresión será nulo.

Eso no dañaría la validez de las funciones de asignación que arrojan excepciones, ya que tienen que obedecer §3.7.4.1 :

[...] Si tiene éxito, devolverá la dirección del inicio de un bloque de almacenamiento cuya longitud en bytes será al menos tan grande como el tamaño solicitado. [...] El puntero devuelto se alineará adecuadamente de modo que se pueda convertir en un puntero de cualquier tipo de objeto completo con un requisito de alineación fundamental (3.11) y luego se utilizará para acceder al objeto o matriz en el almacenamiento asignado (hasta el el almacenamiento es desasignado explícitamente por una llamada a una función de desasignación correspondiente).

Y §5.3.4,14 :

[Nota: cuando la función de asignación devuelve un valor distinto de nulo, debe ser un puntero a un bloque de almacenamiento en el que se ha reservado espacio para el objeto. Se supone que el bloque de almacenamiento está apropiadamente alineado y del tamaño solicitado. [...] -finalización]

Obviamente, una ubicación nueva que simplemente devuelve el puntero dado, no puede verificar razonablemente el tamaño de almacenamiento y la alineación. Por lo tanto,

§18.6.1.3.1 sobre la ubicación nueva dice

[...] Las disposiciones de (3.7.4) no se aplican a estas formas de ubicación reservadas de operador nuevo y eliminación del operador.

(Supongo que se olvidó de mencionar §5.3.4,14 en ese lugar).

Sin embargo, juntos, estos párrafos dicen indirectamente "si pasas un puntero a las funciones de ordenamiento, obtienes UB, porque se viola §5.3.4,14". Por lo tanto, depende de usted comprobar la cordura de cualquier poitner dado a la ubicación nueva.

En ese espíritu, y con el §5.3.4,13 reescrito, el estándar podría despojar al no noexcept de la colocación nueva, lo que lleva a una adición a esa conclusión indirecta: "... y si pasa nulo, también obtiene UB" . Por otro lado, es mucho menos probable que tenga un puntero desviado o un puntero a muy poca memoria que tener un puntero nulo.

Sin embargo, esto eliminaría la necesidad de verificar contra nulo, y encajaría bien con la filosofía "no pague por lo que no necesita". La función de asignación en sí misma no debería verificarse, porque el §18.6.1.3.1 lo dice explícitamente.

Para redondear las cosas, uno podría considerar agregar una segunda sobrecarga

void* operator new(std::size_t size, void* ptr, const std::nothrow_t&) noexcept;

Lamentablemente, proponer esto al comité es poco probable que resulte en un cambio, ya que rompería el código existente dependiendo de que la ubicación esté bien con punteros nulos.