tipos programas lenguaje ejemplos descargar datos comandos caracteristicas c++ memory-management c++14

programas - Contradicción aparente entre el libro de Stroustrup y el estándar de C++



lenguaje c++ pdf (6)

La cita del estándar habla del valor pasado al operador new; La cita de Stroustrup se refiere a qué operador nuevo hace con el valor. Los dos son bastante independientes; el requisito es solo que el asignador asigne al menos la cantidad de almacenamiento que se solicitó. Los asignadores a menudo asignan más espacio del que se solicitó. Lo que hagan con ese espacio adicional depende de la implementación; a menudo es solo relleno Tenga en cuenta que incluso si lee los requisitos de manera restringida, el asignador debe asignar el número exacto de bytes solicitados, la asignación de más está permitida según la regla "como si", porque ningún programa portátil puede detectar la cantidad de memoria asignada.

Estoy intentando entender el siguiente párrafo de "El lenguaje de programación de C ++" de Stroustrup en la página 282 (el énfasis es mío):

Para desasignar el espacio asignado por nuevo, eliminar y eliminar [] debe poder determinar el tamaño del objeto asignado. Esto implica que un objeto asignado utilizando la implementación estándar de new ocupará un poco más de espacio que un objeto estático. Como mínimo, se necesita espacio para mantener el tamaño del objeto . Generalmente se usan dos o más palabras por asignación para la administración de la tienda libre. La mayoría de las máquinas modernas utilizan palabras de 8 bytes. Esta sobrecarga no es significativa cuando asignamos muchos objetos u objetos grandes, pero puede ser importante si asignamos muchos objetos pequeños (por ejemplo, puntos o ints) en la tienda gratuita.

Tenga en cuenta que el autor no diferencia si el objeto es una matriz, o no, en la oración resaltada anteriormente.

Pero de acuerdo con el párrafo §5.3.4 / 11 en C ++ 14, tenemos (mi énfasis):

Cuando una nueva expresión llama a una función de asignación y esa asignación no se ha extendido, la 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 está creando solo si el objeto es una matriz.

Puede que me esté faltando algo, pero me parece que tenemos una contradicción en esas dos afirmaciones. Entendí que el espacio adicional requerido era solo para los objetos de matriz, y que este espacio adicional contendría la cantidad de elementos en la matriz, no el tamaño de la matriz en bytes .


No estoy seguro de que ambos hablen de lo mismo ...

Parece que Stroustrup está hablando de una asignación de memoria más general, que inherentemente utiliza datos adicionales para administrar los fragmentos libres / asignados. Creo que no está hablando del valor del tamaño pasado a new sino de lo que realmente sucede en un nivel inferior. Probablemente diría: cuando pidas 10 bytes, la máquina probablemente usará un poco más de 10 bytes. Usar la implementación estándar parece ser importante aquí.

Mientras que el estándar habla sobre el valor pasado a la función.

Uno habla de implementación mientras que el otro no.


No hay contradicción entre estas dos cosas. La función de asignación obtiene el tamaño, y casi con seguridad tiene que asignar un poco más que eso para que conozca el tamaño nuevamente si se llama a la función de desasignación.

Cuando se asigna una matriz de objetos que tienen un destructor no trivial, la implementación necesita alguna forma de saber cuántas veces se debe llamar al destructor cuando se llama a delete[] . Se permite que las implementaciones asignen algo de espacio adicional junto con la matriz para almacenar esta información adicional, aunque no todas las implementaciones funcionan de esta manera.


No hay contradicción entre los dos párrafos.

El párrafo de la Norma analiza las reglas del primer argumento que se pasa a la función de asignación.

El párrafo de Stroustrup no habla sobre el primer argumento que tiene el tipo std :: size_t, pero explica la asignación en sí misma, que es "dos o más palabras" más grande que lo que indica el nuevo, y que todo programador debe saber.

La explicación de Stroustrup es más bajo nivel, esa es la diferencia. Pero no hay contradicción.


No hay contradicción, porque "precisamente el tamaño del objeto" es una posible implementación de "como mínimo, el tamaño del objeto".

El número 42 es al menos 42.


Si llama new en un tipo T , el operator new sobrecargado operator new que puede invocarse pasará exactamente sizeof(T) .

Si implementa un new (o un asignador) propio que usa un almacenamiento de memoria diferente (es decir, no solo reenviando a otra llamada a new o malloc etc.), tendrá ganas de almacenar información para limpiar la asignación más adelante. , cuando se produce la delete . Una forma típica de hacer esto es obtener un bloque de memoria un poco más grande y almacenar la cantidad de memoria solicitada al inicio, y luego devolver un puntero a la memoria que adquirió.

Esto es aproximadamente lo que hacen la mayoría de las implementaciones estándar de new (y malloc do).

Entonces, mientras que solo necesita sizeof(T) bytes para almacenar una T , la cantidad de bytes consumidos por new / malloc es más que sizeof(T) . A esto se refiere Stroustrup: cada asignación dinámica tiene una sobrecarga real, y esa sobrecarga puede ser considerable si hace muchas asignaciones pequeñas.

Hay algunos asignadores que no necesitan esa habitación extra "antes" de la asignación. Por ejemplo, un asignador de ámbito de pila que no elimina nada hasta que sale del ámbito. O uno que asigna desde tiendas de bloques de tamaño fijo y utiliza un campo de bits para describir cuáles están en uso.

En este caso, la información contable no se almacena adyacente a los datos, o hacemos que la información contable sea implícita en el estado del código (asignadores de ámbito).

Ahora, en el caso de las matrices, el compilador de C ++ es libre de llamar al operator new[] con una cantidad de memoria solicitada mayor que sizeof(T)*n cuando se asigna T[n] . Esto se hace mediante un new código (no el operator new ) generado por el compilador cuando solicita su sobrecarga de memoria.

Esto se realiza tradicionalmente en tipos con destructores no triviales para que el tiempo de ejecución de C ++ pueda, cuando se llama a delete[] , iterar sobre cada uno de los elementos y llamar .~T() en ellos. Obtiene un truco similar, donde mete n en la memoria antes de la matriz que está utilizando, luego hace aritmética de punteros para extraerla en el momento de la eliminación.

El estándar no lo exige , pero es una técnica común (clang y gcc lo hacen al menos en algunas plataformas, y creo que MSVC también lo hace). Se necesita algún método para calcular el tamaño de la matriz; este es solo uno de ellos

Para algo sin un destructor (como char ) o trivial (como struct foo{ ~foo()=default; } , n no es necesario para el tiempo de ejecución, por lo que no tiene que almacenarlo. "naw, no lo guardaré".

Aquí hay un ejemplo en vivo .

struct foo { static void* operator new[](std::size_t sz) { std::cout << sz << ''/'' << sizeof(foo) << ''='' << sz/sizeof(foo) << "+ R(" << sz%sizeof(foo) << ")" << ''/n''; return malloc(sz); } static void operator delete[](void* ptr) { free(ptr); } virtual ~foo() {} }; foo* test(std::size_t n) { std::cout << n << ''/n''; return new foo[n]; } int main(int argc, char**argv) { foo* f = test( argc+10 ); std::cout << *std::prev(reinterpret_cast<std::size_t*>(f)) << ''/n''; }

Si se ejecuta con 0 argumentos, imprime 11 , 96/8 = 12 R(0) y 11 .

El primero es el número de elementos asignados, el segundo es la cantidad de memoria asignada (que suma un valor de 11 elementos, más 8 bytes - sizeof(size_t) sospecho), lo último es lo que encontramos justo antes del inicio de la matriz de 11 elementos (un size_t con el valor 11 ).

El acceso a la memoria antes del inicio de la matriz es un comportamiento naturalmente indefinido, pero lo hice para exponer algunos detalles de la implementación en gcc / clang. El punto es que pidieron un extra de 8 bytes (como se predijo), y sí que almacenaron el valor 11 allí (el tamaño de la matriz).

Si cambia ese 11 a 2 , una llamada para delete[] realmente eliminará el número incorrecto de elementos.

Otras soluciones (para almacenar cuán grande es la matriz) son naturalmente posibles. Como ejemplo, si sabe que no está llamando una sobrecarga de new y conoce detalles de su asignación de memoria subyacente, podría reutilizar los datos que usa para conocer el tamaño de su bloque para determinar la cantidad de elementos, y así ahorrar un tamaño extra. de la memoria. Esto requiere saber que su asignador subyacente no lo asignará en exceso, y que almacena los bytes utilizados en un desplazamiento conocido al puntero de datos.

O, en teoría, un compilador podría construir un puntero separado - mapa de tamaño.

Desconozco a los compiladores que hacen cualquiera de estos, pero ninguno de los dos me sorprendería.

Permitir esta técnica es de lo que habla el estándar de C ++. Para la asignación de matrices, el operator new código del compilador (no el operator new ) tiene permiso para pedirle al operator new memoria adicional. Para la asignación sin arreglos, el new compilador no tiene permitido pedirle al operator new una memoria extra, debe solicitar la cantidad exacta. (Creo que puede haber excepciones para la fusión de asignación de memoria?)

Como puedes ver, las dos situaciones son diferentes.