Articulo de referencia

Sintaxis de colocación

La sintaxis de ubicación en el lenguaje de programación C++ se refiere a la sintaxis para especificar explícitamente la gestión de memoria de objetos individuales (es decir, su ...

La sintaxis de ubicación en el lenguaje de programación C++ se refiere a la sintaxis para especificar explícitamente la gestión de memoria de objetos individuales (es decir, su "ubicación" en la memoria ). Normalmente, cuando se crea un objeto dinámicamente, se invoca una función de asignación de memoria que asigna memoria al objeto y lo inicializa dentro de la memoria recién asignada. La sintaxis de ubicación permite al programador proporcionar argumentos adicionales a la función de asignación. Un uso común es proporcionar un puntero a una región de almacenamiento adecuada donde se pueda inicializar el objeto, separando así la asignación de memoria de la construcción del objeto.

Las versiones de "ubicación" de los newoperadores deletey funciones se conocen como ubicación newy ubicación delete. [ 1 ] Una newexpresión , ya sea de ubicación o de otro tipo, llama a una newfunción , también conocida como función de asignación, cuyo nombre es operator new. De manera similar, una deleteexpresión llama a una deletefunción , también conocida como función de desasignación, cuyo nombre es operator delete. [ 2 ] [ 3 ]

Cualquier newexpresión que utilice la sintaxis de colocación es una newexpresión de colocación, y cualquier operator newfunción operator deleteque tome más del primer parámetro obligatorio ( size_t) es una colocación newo deletefunción de colocación. [ 4 ] Una newfunción de colocación toma dos parámetros de entrada: size_ty void*.

Historia

En versiones anteriores de C++, no existía la asignación `new` ; en su lugar, los desarrolladores utilizaban la asignación explícita thisdentro de los constructores para lograr un efecto similar. [ 5 ] Esta práctica quedó obsoleta y fue eliminada por C++98 , y la tercera edición del libro de Stroustrup, The C++ Programming Language , no menciona esta técnica.

Expresiones

La sintaxis de C++ para una newexpresión que no es de colocación es [ 2 ]

new new-type-id ( optional-initializer-expression-list )

La sintaxis de colocación agrega una lista de expresiones inmediatamente después de la newpalabra clave. Esta lista de expresiones es la colocación. Puede contener cualquier número de expresiones. [ 2 ] [ 3 ] [ 6 ]

new ( expression-list ) new-type-id ( optional-initializer-expression-list )

Funciones

Las newfunciones de colocación son sobrecargas de las funciones que no son de colocación new. La declaración de las funciones que no son de colocación new, para expresiones que no son de arreglos y para arreglos newrespectivamente, es: [ 7 ] [ 8 ]

void * operator new ( size_t n ); void * operator new []( size_t n );

La biblioteca estándar de C++ proporciona dos sobrecargas de ubicación para cada una de estas funciones. Sus declaraciones son: [ 7 ] [ 8 ]

void * operator new ( size_t n , const nothrow_t & t ) noexcept ; void * operator new ( size_t n , void * p ) noexcept ; void * operator new []( size_t n , const nothrow_t & t ) noexcept ; void * operator new []( size_t n , void * p ) noexcept ;

En todas las sobrecargas, el primer parámetro de la operator newfunción es de tipo size_t, que cuando se llama a la función se pasa como argumento especificando la cantidad de memoria, en bytes, que se asignará. Todas las funciones deben devolver un tipo void*, que es un puntero al almacenamiento que la función asigna. [ 2 ]

También existen deletefunciones de colocación. Son versiones sobrecargadas de las deletefunciones que no son de colocación. Las deletefunciones que no son de colocación se declaran como: [ 7 ] [ 8 ]

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

La biblioteca estándar proporciona dos sobrecargas de ubicación para cada una de estas funciones. Sus declaraciones son: [ 7 ] [ 8 ]

void operator delete ( void * p , const nothrow_t & t ) noexcept ; void operator delete ( void * p , void * q ) noexcept ; void operator delete []( void * p , const nothrow_t & t ) noexcept ; void operator delete []( void * p , void * q ) noexcept ;

En todas las sobrecargas, el primer parámetro de la operator deletefunción es de tipo void*, que es la dirección del almacenamiento que se va a desasignar. [ 2 ]

newLos operadores deletedeben residir en el espacio de nombres global y no pueden tener enlaces estáticos . [ 2 ]

Usar

La sintaxis de ubicación tiene cuatro usos principales: ubicación predeterminada, prevención de excepciones , asignadores personalizados y depuración .

Ubicación predeterminada

Las sobrecargas de colocación de operator newy operator deleteque emplean un parámetro adicional void*se utilizan para la colocación predeterminada, también conocida como colocación de punteros . Sus definiciones por la biblioteca estándar, que no está permitido que un programa C++ reemplace o sobrescriba, son: [ 7 ] [ 8 ] [ 9 ]

void * operator new ( size_t n , void * p ) noexcept { return p ; }void * operator new []( size_t n , void * p ) noexcept { return p ; }void operator delete ( void * p , void * q ) noexcept {} void operator delete []( void * p , void * q ) noexcept {}

La ubicación predeterminada tiene varios usos.

Bjarne Stroustrup observó originalmente, en su libro El diseño y la evolución de C++ , que la ubicación de punteros newes necesaria para el hardware que espera un objeto determinado en una dirección de hardware específica. También es necesaria para la construcción de objetos que deben residir en un área de memoria determinada, como un área compartida entre varios procesadores de una computadora multiprocesador. [ 10 ]

Sin embargo, otros usos incluyen llamar directamente a un constructor, algo que el lenguaje C++ no permite de otra manera. [ 3 ]

El lenguaje C++ permite que un programa llame directamente a un destructor y, dado que no es posible destruir el objeto usando una deleteexpresión, así es como se destruye un objeto que fue construido a través de una newexpresión de colocación de puntero, es decir , [ 11 ] [ 12 ]p->~X();

Casos de uso

La asignación de memoria newse utiliza para evitar newque el operador asigne memoria (por ejemplo, si ya se ha asignado previamente y se desea colocar el objeto allí), pero aun así se construye el objeto. Algunos ejemplos de situaciones típicas en las que esto puede ser necesario son:

  • Para crear objetos en la memoria compartida entre dos procesos diferentes.
  • Para garantizar que los objetos se creen en memoria no paginable.
  • Para separar la asignación de memoria de la construcción, por ejemplo, al implementar un std::vector<T>(ver el reserve()método).

El constructor, a diferencia de otras funciones, se llama cuando no hay ningún objeto (solo memoria), y cuando termina de ejecutarse, la memoria es ahora un objeto completamente inicializado. Por lo tanto, el constructor no se puede llamar sobre un objeto, sin embargo, debe acceder (e inicializar) miembros no estáticos. Esto hace que llamar al constructor directamente sea un error. La solución es la forma de colocación del operador new.

Este operador se implementa de la siguiente manera:

void * operator new ( size_t count , void * here ) noexcept { return here ; }void * operator new []( size_t count , void * here ) noexcept { return here ; }

Prevención de excepciones

Normalmente, las funciones (sin asignación) newlanzan una excepción, de tipo std::bad_alloc, si encuentran un error, como el agotamiento de toda la memoria disponible. Esta no era la definición de las funciones en el Manual de Referencia Anotado de C++ de Stroustrup , sino un cambio realizado por el comité de estandarización cuando se estandarizó el lenguaje C++. El comportamiento original de las funciones, que consistía en devolver un puntero nulo cuando ocurría un error, es accesible mediante la sintaxis de asignación. [ 3 ] [ 4 ] [ 6 ]

El uso de esto requiere un encabezado<new> de la biblioteca estándar (o, equivalentemente, del módulostd ). Este encabezado declara el std::nothrowobjeto global, que es de tipo std::nothrow_t(también declarado en el encabezado), que se utiliza para llamar a las newfunciones sobrecargadas que se declaran como tomando const std::nothrow_t&como su segundo parámetro. Por ejemplo: [ 9 ]

importar std ;usando std :: nothrow ;struct X { // ... };int main () { // Llama a la función operator new(size_t, const nothrow_t&) y (si tiene éxito) construye el objeto. X * p = new ( nothrow ) X ; if ( p ) { // Se ha asignado espacio de almacenamiento y se ha llamado al constructor. delete p ; } else { // Ha ocurrido un error. No se ha asignado espacio de almacenamiento ni se ha construido ningún objeto. } return 0 ; }

Asignadores personalizados

La sintaxis de colocación también se emplea para asignadores personalizados . Esto no utiliza ninguna de las funciones de asignación y desasignación del encabezado de la biblioteca estándar <new>, sino que requiere que los programadores escriban sus propias funciones de asignación y desasignación, sobrecargadas para tipos definidos por el usuario. Por ejemplo, se podría definir una clase de asignador de arena de la siguiente manera: [ 7 ] [ 8 ]

importar std ;clase Arena { público : // ...void * allocate ( size_t n ) { // Método de asignación... }void deallocate ( void * p ) { // Método de desasignación... } };

Tenga en cuenta que la asignación de arena se proporciona en la biblioteca estándar de C++ mediante la std::pmr::monotonic_buffer_resourceclase. [ 13 ]

De manera similar, defina las funciones personalizadas de asignación y desasignación de ubicación de la siguiente manera: [ 7 ] [ 8 ]

void * operator new ( size_t size , Arena & arena ) { return arena . allocate ( size ); }void operator delete ( void * p , Arena & arena ) { arena . deallocate ( p ); }

El programa emplearía la sintaxis de colocación para asignar objetos utilizando diferentes instancias de la Arenaclase de la siguiente manera: [ 7 ] [ 8 ]

Arena arena1 ; Arena arena2 ; X * p1 = nuevo ( arena1 ) X ; X * p2 = nuevo ( arena2 ) X ;

Destruir un objeto cuyo almacenamiento se asigna de esta manera requiere cierta precaución. Dado que no existe deleteuna expresión de ubicación, no se puede utilizar para invocar el desasignador personalizado. Se debe escribir una función de destrucción que invoque el desasignador personalizado o llamar deletedirectamente a la función de ubicación como una llamada a función. [ 11 ] [ 7 ] [ 8 ]

El primero se parecería a: [ 8 ]

void destroy ( X * p , Arena & arena ) { p ->~ X (); // Primero , invoca el destructor explícitamente. arena.deallocate ( p ); // Luego, llama directamente a la función deallocate. }

que se invocaría desde un programa de la siguiente manera:

Arena arena ; X * p = nuevo ( arena ) X ; // ... destruir ( p , arena );

Esto último implicaría simplemente escribir la invocación del destructor y la llamada a la función delete en el programa: [ 7 ] [ 14 ]

Arena arena ; X * p = new ( arena ) X ; // ... p ->~ X (); // Primero invoca el destructor explícitamente. operator delete ( p , arena ); // Luego llama a la función desasignadora indirectamente a través de operator delete(void*, Arena&).

Un error común es intentar usar una deleteexpresión para deleteel objeto. Esto provoca que operator deletese llame a la función incorrecta. Dewhurst recomienda dos estrategias para evitar este error. La primera es asegurarse de que cualquier asignador personalizado dependa de la función global no posicional de la biblioteca estándar operator newy, por lo tanto, no sea más que un simple envoltorio alrededor de la gestión de memoria de la biblioteca C++. La segunda es crear newfunciones deletepara clases individuales y personalizar la gestión de memoria a través de los miembros de la función de clase en lugar de usar la sintaxis de posición. [ 14 ]

Depuración

La asignación de memoria newtambién puede utilizarse como una herramienta de depuración sencilla, para permitir que los programas impriman el nombre del archivo y el número de línea del código fuente donde ha fallado una asignación de memoria. Esto no requiere la inclusión del encabezado de la biblioteca estándar <new>, pero sí requiere la inclusión de un encabezado que declare cuatro funciones de asignación de memoria y un reemplazo de macro para la newpalabra clave que se utiliza en newlas expresiones. Por ejemplo, dicho encabezado contendría: [ 9 ] [ 15 ]

#if defined(DEBUG_NEW) void * operator new ( size_t size , const char * file , int line ); void * operator new []( size_t size , const char * file , int line ); void operator delete ( void * p , const char * file , int line ); void operator delete []( void * p , const char * file , int line ); #define New new(__FILE__, __LINE__) #else #define New new #endif

Esto se emplearía en un programa de la siguiente manera: [ 9 ] [ 15 ]

X * p = Nueva X ;

Las funciones de colocación personalizadas newmanejarían entonces el uso de la información de archivo y número de línea proporcionada en caso de una excepción. Por ejemplo: [ 9 ] [ 15 ]

importar std ;using std :: bad_alloc ; using std :: nothrow ;clase FailedAllocationException : public bad_alloc { private : const char * file ; int line ; public : FailedAllocationException ( const char * file , int line ) : bad_alloc (), file { file }, line { line } {} };void * operator new ( size_t size , const char * file , int line ) { if ( void * p = :: operator new ( size , nothrow )) { return p ; } throw FailedAllocationException ( file , line ); }

Desde C++26 , la colocación newse puede utilizar en contextos constexpr .

importar std ;plantilla < typename T > clase Box { privado : alignas ( T ) unsigned char storage [ sizeof ( T )]; T * ptr = nullptr ;constexpr void destroy () { if ( active ) { std :: destroy_at ( ptr ); // llama manualmente al destructor ptr = nullptr ; } } public : constexpr Box () = default ;constexpr ~ Box () { destruir (); }template < typename ... Args > constexpr T & emplace ( Args && ... args ) { destroy (); T * p = new ( static_cast < void *> ( storage )) T ( std :: forward < Args > ( args )...); return * ptr ; }constexpr T & get () { return * ptr ; } };

Eliminar ubicación

Como se indicó anteriormente, no existe ninguna deleteexpresión de ubicación. No es posible llamar a ninguna función de ubicación operator deletemediante una deleteexpresión. [ 11 ] [ 16 ]

Las deletefunciones de asignación se llaman desde newexpresiones de asignación. En particular, se llaman si el constructor del objeto lanza una excepción. En tal caso, para asegurar que el programa no sufra una fuga de memoria , se llaman las funciones de eliminación de asignación. Una newexpresión de asignación primero llama a la operator newfunción de asignación y luego llama al constructor del objeto sobre el almacenamiento sin procesar devuelto por la función de asignación. Si el constructor lanza una excepción, es necesario liberar ese almacenamiento antes de propagar la excepción al código que ejecutó la newexpresión de asignación, y ese es el propósito de las deletefunciones de asignación. [ 2 ] [ 4 ] [ 11 ] [ 16 ]

La función de eliminación de ubicación que se llama coincide con la newfunción de ubicación que fue invocada por la newexpresión de ubicación. Por lo tanto, por ejemplo, si se ejecuta el siguiente código, la deletefunción de ubicación que se llama será operator delete(void*, const Arena&): [ 2 ] [ 11 ] [ 16 ]

importar std ;usando std :: bad_alloc ;clase Arena { // ... };clase X { público : X () { lanzar bad_alloc ( "Asignación incorrecta" ); } };void * operator new ( size_t n , const Arena & a ) { std :: println ( "Se llamó a Placement new." ); }void operator delete ( void * p , const Arena & a ) { std :: println ( "Se llamó a la función de eliminación de ubicación." ); }int main () { Arena arena ; try { X * p = new ( arena ) X ; } catch ( const bad_alloc & e ) { std :: println ( "Excepción capturada: {}" , e.what ( ) ); } return 0 ; }

Por eso, las funciones de asignación de punterosdelete están definidas como operaciones nulas por la biblioteca estándar. Dado que las newfunciones de asignación de punteros no asignan ningún espacio de almacenamiento, no hay espacio de almacenamiento que [ 17 ] liberar en caso de que el constructor del objeto lance una excepción. [ 11 ]

Si no existe una función de eliminación de ubicación coincidente, no se llama a ninguna función de desasignación en caso de que un constructor lance una excepción dentro de una newexpresión de ubicación. También hay algunas implementaciones de C++ (más antiguas) que no admiten la ubicación delete(que, al igual que las funciones de asignación que lanzan excepciones, se añadieron a C++ cuando se estandarizó). En ambos casos, una excepción lanzada por un constructor al asignar usando un asignador personalizado provocará una fuga de memoria. (En el caso de las implementaciones de C++ más antiguas, también se producirá una fuga de memoria con expresiones que no sean de ubicación new). [ 4 ] [ 16 ]

Seguridad

Las expresiones de colocación newson vulnerables a exploits de seguridad. En 2011, Kundu y Bertino [ 17 ] demostraron algunos de los exploits en la colocación new. Algunos de los ataques son ataques de desbordamiento de búfer , desbordamiento de objeto, anulación selectiva de stackguard, subterfugio de puntero virtual y ataques de desalineación de memoria. En 2015, GCC publicó un parche [ 18 ] basado en los hallazgos de [ 17 ] .

Notas

  1. McCluskey 2000
  2. 1 2 3 4 5 6 7 8 Lischner 2003 , págs. 72–73, 128–129, 310, 623–625
  3. 1 2 3 4 Lippman 1997 , págs. 386–389
  4. 1 2 3 4 Meyers 1998
  5. Stroustrup 1991
  6. 1 2 Loudon 2003 , págs. 109–110
  7. 1 2 3 4 5 6 7 8 9 10 Vermeir 2001 , págs. 113-115
  8. 1 2 3 4 5 6 7 8 9 10 Stroustrup 1997 , págs. 255–256, 576
  9. 1 2 3 4 5 Anderson 1998a , págs. 345–356
  10. Stroustrup 1994 , págs. 214
  11. ^ Solter y Kleper 2005 , págs . 458–461 . 
  12. Seed y Cooper 2001 , págs. 435–436
  13. cppreference.com (7 de marzo de 2026). "std::pmr::monotonic_buffer_resource" . cppreference.com . cppreference.com.
  14. 1 2 Dewhurst 2003 , págs. 173–176
  15. 1 2 3 Yongwei 2007
  16. 1 2 3 4 Anderson 1998b , págs. 631–632
  17. 1 2 3 Kundu, Ashish; Bertino, Elisa (junio de 2011). "Una nueva clase de ataques de desbordamiento de búfer". 31.ª Conferencia Internacional sobre Sistemas de Computación Distribuida de 2011. págs. 730–739 . doi : 10.1109/ICDCS.2011.63 . ISBN  978-1-61284-384-1. S2CID 8583476 vía IEEE. 
  18. "Martin Sebor - [ PING ] [ PATCH ] c++/67942 - diagnosticar desbordamiento de búfer de nueva ubicación" . gcc.gnu.org . Consultado el 15 de junio de 2020 .

Referencias

  • Anderson, Gail (1998a). "Gestión de almacenamiento de objetos". Navegando por C++ y el diseño orientado a objetos . Prentice Hall. ISBN 9780135327487.
  • Anderson, Gail (1998b). "Manejo de excepciones". Navegando por C++ y el diseño orientado a objetos . Prentice Hall. ISBN 9780135327487.
  • Buck, Joe (1997-05-12). "3.4. g++ no acepta la nueva sintaxis de colocación" . Preguntas frecuentes sobre el compilador GNU C++ . Recuperado el 26 de noviembre de 2008 .
  • Dewhurst, Stephen C. (2003). "Errores comunes n.° 62: Reemplazando New y Delete globales" . Errores comunes de C++ . Addison-Wesley . ISBN 978-0-321-12518-7.
  • Lischner, Ray (2003). C++ en pocas palabras . O'Reilly. ISBN 9780596002985.
  • Lippman, Stanley B. (1997). C++ Gems . Cambridge University Press. ISBN 9780135705810.
  • Loudon, Kyle (2003). C++ Pocket Reference . O'Reilly. ISBN 9780596004965.
  • "Colocación Nueva/Eliminar" . Lenguaje y biblioteca C++ . Glen McCluskey & Associates LLC. 26 de junio de 2000. Archivado del original el 18 de abril de 2006. Consultado el 26 de noviembre de 2008 .
  • Meyers, Scott (1998-04-01). "Placement new and placement delete" . Dr. Dobb's Journal . United Business Media LLC.
  • Seed, Graham M.; Cooper, Barry J. (2001). Introducción a la programación orientada a objetos en C++ (Segunda  edición). Springer. ISBN 1-85233-450-9.
  • Solter, Nicholas; Kleper, Scott (2005). Professional C++ . Wiley. ISBN 9780764574849.
  • Stroustrup, Bjarne (julio de 1991). El lenguaje de programación C++ (2.ª  ed.). Pearson Education Canada. ISBN 978-0-201-53992-9.
  • Stroustrup, Bjarne (1994). "Gestión de memoria". Diseño y evolución de C++ . Addison-Wesley . ISBN 978-0-201-54330-8.
  • Stroustrup, Bjarne (1997). El lenguaje de programación C++ (3.ª  ed.). Addison-Wesley . ISBN 978-0-201-88954-3.
  • Vermeir, Dirk (2001). Programación multiparadigma utilizando C++ . Saltador. ISBN 9781852334833.
  • Yongwei, Wu (31 de diciembre de 2007). "Un detector de fugas de memoria multiplataforma" . Página de programación de Wu Yongwei . Consultado el 26 de noviembre de 2008 .

Lecturas adicionales

  • Franek, Frantisek (2004). La memoria como concepto de programación en C y C++ . Cambridge University Press . ISBN 978-0-521-52043-0.
  • Cline, Marshall (25 de septiembre de 2006). "11.10: ¿Qué es "placement new" y por qué debería usarlo?" . Preguntas frecuentes de C++ Lite . Recuperado el 26 de noviembre de 2008 .
  • "New Operator de C++" . Compiladores de IBM para Mac OS X. IBM . 2003. Consultado el 27 de noviembre de 2008 .
  • "La nueva función del operador" . MSDN . Microsoft . Consultado el 27 de noviembre de 2008 .
  • "Nuevo operador (C++)" . MSDN . Microsoft . Consultado el 27 de noviembre de 2008 .
Obtenido de " https://en.wikipedia.org/w/index.php?title=Placement_syntax&oldid=1358377631 "