tutorial studio programacion móviles español desarrollo desarrollar curso aprende aplicaciones c++ c++14 delete-operator

c++ - studio - programacion android pdf 2018



¿Cómo utilizaría los operadores de tamaño eliminar/eliminar[] y por qué son mejores? (3)

C ++ 14 introdujo versiones "dimensionadas" del operator delete , es decir

void operator delete( void* ptr, std::size_t sz );

y

void operator delete[]( void* ptr, std::size_t sz );

Al leer N3536 , parece que esos operadores se introdujeron para aumentar el rendimiento. Sé que el asignador típico usado por el operator new "almacena" el tamaño de la memoria masiva en alguna parte, y así es como el operator delete típico de "saber" cuánta memoria hay que devolver a la tienda libre.

Sin embargo, no estoy seguro de por qué las versiones de "tamaño" de la operator delete del operator delete ayudarán en términos de rendimiento. Lo único que puede acelerar las cosas es una operación de lectura menos con respecto al tamaño del bloque de control. ¿Es esta la única ventaja?

Segundo, ¿cómo puedo lidiar con la versión de matriz? AFAIK, el tamaño de la matriz asignada no es simplemente sizeof(type)*number_elements , pero puede haber algunos bytes adicionales asignados ya que la implementación puede usar esos bytes como bytes de control. ¿Qué "tamaño" debo pasar al operator delete[] en este caso? ¿Puedes dar un breve ejemplo de uso?


Alguna información relevante: Actualmente, la implementación de VS 17 de eliminación de tamaño [] parece rota. Siempre devuelve el tamaño del puntero genérico (void *). El g ++ 7.3.1 da el tamaño de la matriz completa más 8 bytes de sobrecarga. No lo he probado en otros compiladores, pero como ves ninguno de los dos da el resultado esperado. Debido a su utilidad, como se alude en la respuesta seleccionada, la utilidad principal entra en juego cuando tiene sus asignadores personalizados, ya sea para pasar a los contenedores stl o simplemente para la administración de la memoria local. En estos casos, podría ser muy útil que se te devuelva el tamaño de la matriz de tamaño de usuario, para que puedas liberar el tamaño adecuado de tus asignadores. Puedo ver que es posible evitar tener que usar esto sin embargo. Aquí hay un código que puede usar para probar la "corrección" de la implementación de eliminación [] del tamaño en su compilador:

#include <iostream> #include <sstream> #include <string> std::string true_cxx = #ifdef __clang__ "clang++"; #elif _MSC_VER "MVC"; #else "g++"; #endif std::string ver_string(int a, int b, int c) { std::ostringstream ss; ss << a << ''.'' << b << ''.'' << c; return ss.str(); } std::string true_cxx_ver = #ifdef __clang__ ver_string(__clang_major__, __clang_minor__, __clang_patchlevel__); #elif _MSC_VER #ifdef _MSC_FULL_VER #if _MSC_FULL_VER == 170060315 "MSVS 2012; Platform Toolset v110"; #elif _MSC_FULL_VER == 170051025 "MSVS 2012; Platform Toolset v120_CTP_Nov2012"; #elif _MSC_FULL_VER == 180020617 "MSVS 2013; Platform Toolset v120"; #elif _MSC_FULL_VER == 191426431 "MSVS 2017; Platform Toolset v140"; #else "Not recognized"; #endif #endif // _MSC_FULL_VER #else ver_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); #endif // sized class-specific deallocation functions struct X { static void operator delete(void* ptr, std::size_t sz) { std::cout << "custom delete for size " << sz << ''/n''; ::operator delete(ptr); } static void operator delete[](void* ptr, std::size_t sz) { std::cout << "custom delete[] for size " << sz << ''/n''; ::operator delete(ptr); } char16_t c[2]; }; int main() { X* p1 = new X; delete p1; X* p2 = new X[10]; for (int i = 0; i < 10; ++i) p2[i] = X{ (char16_t)i }; std::cout << "Compiler: "<<true_cxx.c_str()<<": Version:" << true_cxx_ver.c_str() << std::endl; delete[] p2; }


Para operator delete el argumento de size a operator delete C ++ 14 operator delete debe pasar el mismo tamaño que le dio al operator new , que está en bytes. Pero a medida que descubres que es más complicado para matrices. Para saber por qué es más complicado, consulte aquí: la ubicación de la matriz nueva requiere una sobrecarga no especificada en el búfer.

Así que si haces esto:

std::string* arr = new std::string[100]

Puede que no sea válido hacer esto:

operator delete[](arr, 100 * sizeof(std::string)); # BAD CODE?

Porque la new expresión original no era equivalente a:

std::string* arr = new (new char[100 * sizeof(std::string)]) std::string[100];

En cuanto a por qué es mejor la delete del tamaño de la API, parece que hoy en día no lo es, pero la esperanza es que algunas bibliotecas estándar mejoren el rendimiento de la desasignación porque en realidad no almacenan el tamaño de asignación al lado de cada bloque asignado (el clásico / libro de texto modelo). Para más información, vea aquí: Característica de desasignación de tamaño en la gestión de memoria en C ++ 1y

Y, por supuesto, la razón para no almacenar el tamaño al lado de cada asignación es que es una pérdida de espacio si realmente no lo necesita. Para los programas que hacen muchas asignaciones dinámicas pequeñas (¡que son más populares de lo que deberían ser!), Esta sobrecarga puede ser significativa. Por ejemplo, en el constructor "plain vanilla" std::shared_ptr (en lugar de make_shared ), se asigna dinámicamente un recuento de referencia, por lo que si su asignador almacena el tamaño al lado, puede requerir ingenuamente una sobrecarga del 25%: un "tamaño" entero para el asignador más el bloque de control de cuatro ranuras . Sin mencionar la presión de la memoria: si el tamaño no se almacena junto al bloque asignado, evita cargar una línea desde la memoria en la desasignación: la única información que necesita se proporciona en la llamada a la función (bueno, también debe mirar la arena o la lista libre o lo que sea, pero necesitabas que, en cualquier caso, aún así puedas saltarte una carga).


Tratar con su segunda pregunta primero:

Si está presente, el argumento de tamaño std :: size_t debe ser igual al argumento de tamaño pasado a la función de asignación que devolvió ptr.

Por lo tanto, cualquier espacio adicional que pueda asignarse es responsabilidad de la biblioteca de tiempo de ejecución, no del código del cliente.

La primera pregunta es más difícil de responder bien. La idea principal es (o al menos parece ser) que el tamaño de un bloque a menudo no se almacena justo al lado del propio bloque. En la mayoría de los casos, el tamaño del bloque se escribe, y nunca se vuelve a escribir hasta que el bloque se desasigne. Para evitar que los datos contaminen el caché mientras el bloque está en uso, se puede mantener por separado. Luego, cuando vaya a desasignar el bloque, el tamaño con frecuencia se habrá desplazado al disco, por lo que volver a leerlo es bastante lento.

También es bastante común evitar explícitamente almacenar el tamaño de cada bloque de forma explícita. Un asignador con frecuencia tendrá grupos separados para diferentes tamaños de bloques (por ejemplo, potencias de 2 a 16 o más o menos hasta un par de kilobytes). Asignará un bloque (bastante) grande del sistema operativo para cada grupo, y luego asignará partes de ese bloque grande al usuario. Cuando devuelve una dirección, básicamente busca esa dirección a través de los diferentes tamaños de grupos para encontrar de qué grupo proviene. Si tiene muchas agrupaciones y muchos bloques en cada agrupación, puede ser relativamente lento.

La idea aquí es evitar ambas posibilidades. En un caso típico, sus asignaciones / desasignaciones están más o menos vinculadas a la pila de todos modos, y cuando sean del tamaño que esté asignando, probablemente estarán en una variable local. Cuando desasigna, normalmente estará en (o al menos cerca de) el mismo nivel de la pila donde hizo la asignación, por lo que esa misma variable local estará fácilmente disponible y probablemente no se pagará en el disco (o algo así) porque otras variables almacenadas en las cercanías también están en uso. Para la forma no matricial, la llamada a ::operator new generalmente se derivará de una new expression , y la llamada a ::operator delete de la delete expression correspondiente. En este caso, el código generado para construir / destruir el objeto "sabe" el tamaño que va a solicitar (y destruir) basándose únicamente en el tipo de objeto que se crea / destruye.