c++ - programacion - ¿Cómo elimina[] saber que es una matriz?
matrices c++ (15)
Acepte que el compilador no sabe si es una matriz o no. Depende del programador.
El compilador a veces realiza un seguimiento de la cantidad de objetos que se deben eliminar asignando lo suficiente para almacenar el tamaño de la matriz, pero no siempre es necesario.
Para obtener una especificación completa cuando se asigna almacenamiento adicional, consulte C ++ ABI (cómo se implementan los compiladores): Itanium C ++ ABI: Array Operator new Cookies
De acuerdo, creo que todos estamos de acuerdo en que lo que sucede con el siguiente código no está definido, dependiendo de lo que se pase,
void deleteForMe(int* pointer)
{
delete[] pointer;
}
El puntero podría ser todo tipo de cosas diferentes, por lo que realizar una delete[]
incondicional delete[]
no está definido. Sin embargo, supongamos que de hecho estamos pasando un puntero de matriz,
int main()
{
int* arr = new int[5];
deleteForMe(arr);
return 0;
}
Mi pregunta es, en este caso donde el puntero es una matriz, ¿quién es el que sabe esto? Quiero decir, desde el punto de vista del lenguaje / compilador, no tiene idea de si arr
es o no un apuntador de matriz versus un puntero a un único int. Diablos, ni siquiera sabe si arr
fue creado dinámicamente. Sin embargo, si hago lo siguiente en su lugar,
int main()
{
int* num = new int(1);
deleteForMe(num);
return 0;
}
El sistema operativo es lo suficientemente inteligente como para eliminar solo un int y no seguir un tipo de ''matanza'' eliminando el resto de la memoria más allá de ese punto (contraste eso con strlen
y una cadena no terminada en /0
- mantendrá yendo hasta que llegue a 0).
Entonces, ¿de quién es el trabajo para recordar estas cosas? ¿El sistema operativo mantiene algún tipo de registro en segundo plano? (Quiero decir, me doy cuenta de que comencé esta publicación diciendo que lo que sucede no está definido, pero el hecho es que el escenario de ''matanza'' no ocurre, por lo que en el mundo práctico alguien está recordando).
Depende del tiempo de ejecución que es responsable de la asignación de memoria, de la misma manera que puede eliminar una matriz creada con malloc en el estándar C usando libre. Creo que cada compilador lo implementa de manera diferente. Una forma común es asignar una celda extra para el tamaño de la matriz.
Sin embargo, el tiempo de ejecución no es lo suficientemente inteligente como para detectar si es una matriz o un puntero, debe informarlo, y si se equivoca, o bien no lo elimina correctamente (por ejemplo, ptr en lugar de la matriz), o usted termina tomando un valor no relacionado para el tamaño y causa un daño significativo.
El compilador no sabe que es una matriz, está confiando en el programador. Eliminar un puntero a una sola int
con delete []
daría como resultado un comportamiento indefinido. Su segundo ejemplo main()
es inseguro, incluso si no se bloquea inmediatamente.
El compilador tiene que realizar un seguimiento de cuántos objetos se deben eliminar de alguna manera. Puede hacer esto al asignar lo suficiente para almacenar el tamaño de la matriz. Para obtener más detalles, consulte las preguntas más frecuentes de C ++ .
Esto es muy similar a esta pregunta y tiene muchos de los detalles que está buscando.
Pero baste decir que no es tarea del sistema operativo rastrear nada de esto. En realidad, las bibliotecas de tiempo de ejecución o el administrador de memoria subyacente rastrearán el tamaño de la matriz. Esto generalmente se hace asignando memoria extra por adelantado y almacenando el tamaño de la matriz en esa ubicación (la mayoría utiliza un nodo principal).
Esto se puede ver en algunas implementaciones ejecutando el siguiente código
int* pArray = new int[5];
int size = *(pArray-1);
La respuesta:
int * pArray = new int [5];
int size = * (pArray-1);
Publicado arriba no es correcto y produce un valor no válido. El "-1" cuenta los elementos En el sistema operativo Windows de 64 bits, el tamaño del búfer correcto reside en la dirección Ptr - 4 bytes
No puede usar delete para una matriz, y no puede usar delete [] para una matriz no.
No sabe que es una matriz, es por eso que tiene que proporcionar delete[]
lugar de la antigua delete
normal.
Oye, bueno, depende de lo que asignas con la nueva expresión [] cuando asignas una matriz de construcción en tipos o clase / estructura y no proporcionas tu constructor y destructor, el operador lo tratará como un tamaño "sizeof (object) * numObjects "en lugar de matriz de objetos, por lo tanto, en este caso, el número de objetos asignados no se almacenará en ningún lugar; sin embargo, si asigna matriz de objetos y proporciona el constructor y el destructor en su objeto que la modificación de comportamiento, la nueva expresión asignará 4 bytes más objetos en los primeros 4 bytes para que se pueda invocar el destructor de cada uno de ellos y, por lo tanto, la nueva expresión [] devolverá el puntero desplazado por 4 bytes hacia adelante, que cuando se devuelve la memoria la expresión delete [] llamará a una plantilla de función primero, iterar a través de una matriz de objetos y un destructor de llamadas para cada uno de ellos. He creado este código simple que sobrecarga las nuevas expresiones [] y delete [] y proporciona una función de plantilla para desasignar la memoria y llamar al destructor para cada objeto si es necesario:
// overloaded new expression
void* operator new[]( size_t size )
{
// allocate 4 bytes more see comment below
int* ptr = (int*)malloc( size + 4 );
// set value stored at address to 0
// and shift pointer by 4 bytes to avoid situation that
// might arise where two memory blocks
// are adjacent and non-zero
*ptr = 0;
++ptr;
return ptr;
}
//////////////////////////////////////////
// overloaded delete expression
void static operator delete[]( void* ptr )
{
// decrement value of pointer to get the
// "Real Pointer Value"
int* realPtr = (int*)ptr;
--realPtr;
free( realPtr );
}
//////////////////////////////////////////
// Template used to call destructor if needed
// and call appropriate delete
template<class T>
void Deallocate( T* ptr )
{
int* instanceCount = (int*)ptr;
--instanceCount;
if(*instanceCount > 0) // if larger than 0 array is being deleted
{
// call destructor for each object
for(int i = 0; i < *instanceCount; i++)
{
ptr[i].~T();
}
// call delete passing instance count witch points
// to begin of array memory
::operator delete[]( instanceCount );
}
else
{
// single instance deleted call destructor
// and delete passing ptr
ptr->~T();
::operator delete[]( ptr );
}
}
// Replace calls to new and delete
#define MyNew ::new
#define MyDelete(ptr) Deallocate(ptr)
// structure with constructor/ destructor
struct StructureOne
{
StructureOne():
someInt(0)
{}
~StructureOne()
{
someInt = 0;
}
int someInt;
};
//////////////////////////////
// structure without constructor/ destructor
struct StructureTwo
{
int someInt;
};
//////////////////////////////
void main(void)
{
const unsigned int numElements = 30;
StructureOne* structOne = nullptr;
StructureTwo* structTwo = nullptr;
int* basicType = nullptr;
size_t ArraySize = 0;
/**********************************************************************/
// basic type array
// place break point here and in new expression
// check size and compare it with size passed
// in to new expression size will be the same
ArraySize = sizeof( int ) * numElements;
// this will be treated as size rather than object array as there is no
// constructor and destructor. value assigned to basicType pointer
// will be the same as value of "++ptr" in new expression
basicType = MyNew int[numElements];
// Place break point in template function to see the behavior
// destructors will not be called and it will be treated as
// single instance of size equal to "sizeof( int ) * numElements"
MyDelete( basicType );
/**********************************************************************/
// structure without constructor and destructor array
// behavior will be the same as with basic type
// place break point here and in new expression
// check size and compare it with size passed
// in to new expression size will be the same
ArraySize = sizeof( StructureTwo ) * numElements;
// this will be treated as size rather than object array as there is no
// constructor and destructor value assigned to structTwo pointer
// will be the same as value of "++ptr" in new expression
structTwo = MyNew StructureTwo[numElements];
// Place break point in template function to see the behavior
// destructors will not be called and it will be treated as
// single instance of size equal to "sizeof( StructureTwo ) * numElements"
MyDelete( structTwo );
/**********************************************************************/
// structure with constructor and destructor array
// place break point check size and compare it with size passed in
// new expression size in expression will be larger by 4 bytes
ArraySize = sizeof( StructureOne ) * numElements;
// value assigned to "structOne pointer" will be different
// of "++ptr" in new expression "shifted by another 4 bytes"
structOne = MyNew StructureOne[numElements];
// Place break point in template function to see the behavior
// destructors will be called for each array object
MyDelete( structOne );
}
///////////////////////////////////////////
Sí, el sistema operativo mantiene algunas cosas en el "fondo". Por ejemplo, si ejecuta
int* num = new int[5];
el sistema operativo puede asignar 4 bytes adicionales, almacenar el tamaño de la asignación en los primeros 4 bytes de la memoria asignada y devolver un puntero de desplazamiento (es decir, asigna espacios de memoria de 1000 a 1024 pero el puntero devolvió los puntos a 1004, con ubicaciones de 1000- 1003 almacenando el tamaño de la asignación). Luego, cuando se invoca delete, puede mirar 4 bytes antes de que el puntero se le pase para encontrar el tamaño de la asignación.
Estoy seguro de que hay otras maneras de rastrear el tamaño de una asignación, pero esa es una opción.
Semánticamente, ambas versiones del operador de eliminación en C ++ pueden "comer" cualquier puntero; sin embargo, si se da un puntero a un solo objeto para delete[]
, entonces se producirá UB, lo que significa que puede pasar cualquier cosa, incluido un bloqueo del sistema o nada en absoluto.
C ++ requiere que el programador elija la versión adecuada del operador de eliminación según el tema de desasignación: matriz u objeto único.
Si el compilador pudiera determinar automáticamente si un puntero pasado al operador de eliminación era una matriz de puntero, entonces habría solo un operador de eliminación en C ++, que sería suficiente para ambos casos.
Tengo una pregunta similar a esto. En C, asigna memoria con malloc () (u otra función similar) y la elimina con free (). Solo hay un malloc (), que simplemente asigna una cierta cantidad de bytes. Solo hay un free (), que simplemente toma un puntero como su parámetro.
Entonces, ¿por qué es que en C solo puedes pasar el puntero a la libertad, pero en C ++ debes decir si es una matriz o una sola variable?
La respuesta, he aprendido, tiene que ver con destructores de clase.
Si asigna una instancia de una clase MyClass ...
classes = new MyClass[3];
Y elimínelo con delete, solo puede obtener el destructor para la primera instancia de MyClass llamada. Si usa delete [], puede estar seguro de que se llamará al destructor para todas las instancias en la matriz.
ESTA es la diferencia importante. Si simplemente trabajas con tipos estándar (por ejemplo, int), realmente no verás este problema. Además, debe recordar que el comportamiento para usar eliminar en nuevo [] y eliminar [] en nuevo no está definido, es posible que no funcione de la misma manera en cada compilador / sistema.
Una pregunta que las respuestas dadas hasta ahora no parecen abordar: si las bibliotecas de tiempo de ejecución (no el sistema operativo, en realidad) pueden realizar un seguimiento de la cantidad de elementos en el conjunto, entonces ¿por qué necesitamos la sintaxis delete[]
en absoluto? ? ¿Por qué no se puede usar un solo formulario de delete
para manejar todas las eliminaciones?
La respuesta a esto se remonta a las raíces de C ++ como un lenguaje compatible con C (que ya no se esfuerza realmente por ser). La filosofía de Stroustrup era que el programador no debería tener que pagar por ninguna característica que no esté utilizando. Si no están utilizando matrices, entonces no deberían tener que cargar con el costo de las matrices de objetos para cada porción de memoria asignada.
Es decir, si su código simplemente lo hace
Foo* foo = new Foo;
luego, el espacio de memoria que se asigna para foo
no debe incluir ninguna sobrecarga adicional que se necesitaría para admitir las matrices de Foo
.
Como solo las asignaciones de matriz están configuradas para transportar la información de tamaño de matriz adicional, debe indicar a las bibliotecas de tiempo de ejecución que busquen esa información cuando elimine los objetos. Es por eso que tenemos que usar
delete[] bar;
en lugar de simplemente
delete bar;
si la barra es un puntero a una matriz.
Para la mayoría de nosotros (incluyéndome a mí), esa inquietud acerca de unos pocos bytes adicionales de memoria parece extraña en estos días. Pero todavía hay algunas situaciones donde guardar algunos bytes (de lo que podría ser un número muy alto de bloques de memoria) puede ser importante.
Uno de los enfoques para los compiladores es asignar un poco más de memoria y almacenar el conteo de elementos en el elemento principal.
Ejemplo de cómo se podría hacer: Aquí
int* i = new int[4];
el compilador asignará sizeof (int) * 5 bytes.
int *temp = malloc(sizeof(int)*5)
Almacenará 4
en el primer sizeof(int)
bytes sizeof(int)
*temp = 4;
y establecer i
i = temp + 1;
Así que i
a una matriz de 4 elementos, no 5.
Y
delete[] i;
será procesado de la siguiente manera
int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements if needed
... that are stored in temp + 1, temp + 2, ... temp + 4
free (temp)
simplemente define un destructor dentro de una clase y ejecuta tu código con ambas sintaxis
delete pointer
delete [] pointer
de acuerdo con la salida puede encontrar las soluciones
delete
o delete[]
probablemente liberaría la memoria asignada (memoria apuntada), pero la gran diferencia es que delete
en una matriz no llamará al destructor de cada elemento de la matriz.
De todos modos, mezclar new/new[]
y delete/delete[]
es probablemente UB.