Muy bien, 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 un incondicional delete[]
en él 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 un puntero de matriz frente a un puntero a un solo 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 ir a algún tipo de 'juerga asesina' eliminando el resto de la memoria más allá de ese punto (contraste eso con strlen
y una \0
cadena no terminada, continuará hasta que hits 0).
Entonces, ¿de quién es el trabajo de 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 la 'ola de asesinatos' no ocurre, por lo que en el mundo práctico alguien está recordando).
Respuestas:
El compilador no sabe que es una matriz, confía en el programador. Eliminar un puntero a un solo
int
condelete []
resultaría en un comportamiento indefinido. Su segundomain()
ejemplo es inseguro, incluso si no se bloquea de inmediato.El compilador tiene que realizar un seguimiento de cuántos objetos deben eliminarse de alguna manera. Puede hacer esto al sobreasignar lo suficiente como para almacenar el tamaño de la matriz. Para obtener más detalles, consulte las preguntas frecuentes de C ++ Super .
fuente
Una pregunta que las respuestas dadas hasta ahora no parecen abordar: si las bibliotecas de tiempo de ejecución (no el sistema operativo, realmente) pueden realizar un seguimiento de la cantidad de cosas en la matriz, entonces ¿por qué necesitamos la
delete[]
sintaxis? ¿Por qué no sedelete
puede usar un solo formulario 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 las funciones que no están utilizando. Si no están usando matrices, entonces no deberían tener que cargar con el costo de las matrices de objetos por cada porción de memoria asignada.
Es decir, si su código simplemente lo hace
entonces el espacio de memoria asignado
foo
no debería incluir ninguna sobrecarga adicional que sería necesaria para admitir matricesFoo
.Dado que solo las asignaciones de matriz se configuran para transportar la información adicional del tamaño de la matriz, debe indicar a las bibliotecas de tiempo de ejecución que busquen esa información cuando elimine los objetos. Es por eso que necesitamos usar
en lugar de solo
si bar es un puntero a una matriz.
Para la mayoría de nosotros (incluido yo mismo), esa inquietud acerca de unos pocos bytes adicionales de memoria parece pintoresca en estos días. Pero todavía hay algunas situaciones en las que guardar algunos bytes (de lo que podría ser un número muy alto de bloques de memoria) puede ser importante.
fuente
Sí, el sistema operativo mantiene algunas cosas en segundo plano. Por ejemplo, si corres
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 compensación (es decir, asigna espacios de memoria 1000 a 1024 pero el puntero devolvió puntos a 1004, con ubicaciones 1000- 1003 almacenando el tamaño de la asignación). Luego, cuando se llama a delete, puede mirar 4 bytes antes de pasarle el puntero para encontrar el tamaño de la asignación.
Estoy seguro de que hay otras formas de rastrear el tamaño de una asignación, pero esa es una opción.
fuente
for(int i = 0; i < *(arrayPointer - 1); i++){ }
Esto es muy similar a esta pregunta y tiene muchos de los detalles que está buscando.
Pero basta con decir que no es el trabajo 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 adicional por adelantado y almacenando el tamaño de la matriz en esa ubicación (la mayoría usa un nodo principal).
Esto se puede ver en algunas implementaciones ejecutando el siguiente código
fuente
size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)
lugardelete
odelete[]
probablemente ambos liberarían la memoria asignada (memoria puntiaguda), pero la gran diferencia es quedelete
en una matriz no se llamará al destructor de cada elemento de la matriz.De todos modos, mezcla
new/new[]
ydelete/delete[]
probablemente sea UB.fuente
No sabe que es una matriz, es por eso que debe suministrar en
delete[]
lugar de la antiguadelete
.fuente
Tenía una pregunta similar a esta. En C, asigna memoria con malloc () (u otra función similar) y la elimina con free (). Solo hay un malloc (), que simplemente asigna un cierto número de bytes. Solo hay un free (), que simplemente toma un puntero como parámetro.
Entonces, ¿por qué en C puedes simplemente pasar el puntero para liberarlo, pero en C ++ debes decir si se trata de una matriz o una sola variable?
La respuesta, he aprendido, tiene que ver con destructores de clases.
Si asigna una instancia de una clase MyClass ...
Y elimínelo con eliminar, 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 de la matriz.
Esta es la diferencia importante. Si simplemente está trabajando con tipos estándar (por ejemplo, int), realmente no verá este problema. Además, debe recordar que el comportamiento para usar delete en new [] y delete [] en new no está definido: puede que no funcione de la misma manera en cada compilador / sistema.
fuente
El tiempo de ejecución es el 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 free. Creo que cada compilador lo implementa de manera diferente. Una forma común es asignar una celda adicional para el tamaño de la matriz.
Sin embargo, el tiempo de ejecución no es lo suficientemente inteligente como para detectar si es o no una matriz o un puntero, debe informarlo y, si está equivocado, no lo elimina correctamente (por ejemplo, ptr en lugar de matriz), o terminas tomando un valor no relacionado para el tamaño y causando un daño significativo.
fuente
UNO DE LOS enfoques para los compiladores es asignar un poco más de memoria y almacenar el recuento de elementos en el elemento principal.
Ejemplo de cómo se podría hacer: Aquí
el compilador asignará sizeof (int) * 5 bytes.
Se almacenará
4
en los primerossizeof(int)
bytesy establecer
i
Entonces
i
apunta a una matriz de 4 elementos, no 5.Y
será procesado de la siguiente manera
fuente
Semánticamente, ambas versiones del operador delete en C ++ pueden "comer" cualquier puntero; sin embargo, si se da un puntero a un solo objeto a
delete[]
, entonces UB resultará, lo que significa que puede suceder 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 la 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 punteros, entonces solo habría un operador de eliminación en C ++, lo que sería suficiente para ambos casos.
fuente
Acuerde que el compilador no sabe si es una matriz o no. Depende del programador.
El compilador a veces realiza un seguimiento de cuántos objetos deben eliminarse al sobreasignar lo suficiente como 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: nuevas cookies del operador de matriz
fuente
No puede usar eliminar para una matriz, y no puede usar eliminar [] para una no matriz.
fuente
"comportamiento indefinido" simplemente significa que la especificación del lenguaje no garantiza lo que sucederá. No significa necesariamente que algo malo sucederá.
Normalmente hay dos capas aquí. El administrador de memoria subyacente y la implementación de C ++.
En general, el administrador de memoria recordará (entre otras cosas) el tamaño del bloque de memoria asignado. Esto puede ser mayor que el bloque que solicitó la implementación de C ++. Por lo general, el administrador de memoria almacenará sus metadatos antes del bloque de memoria asignado.
La implementación de C ++ generalmente solo recordará el tamaño de la matriz si necesita hacerlo para sus propios fines, generalmente porque el tipo tiene un destructor no trival.
Entonces, para los tipos con un destructor trivial, la implementación de "eliminar" y "eliminar []" suele ser la misma. La implementación de C ++ simplemente pasa el puntero al administrador de memoria subyacente. Algo como
Por otro lado, para los tipos con un destructor no trivial, "delete" y "delete []" probablemente sean diferentes. "eliminar" sería algo así (donde T es el tipo al que apunta el puntero)
Mientras que "eliminar []" sería algo así.
fuente
iterar a través de una matriz de objetos y llamar al destructor para cada uno de ellos. He creado este código simple que sobrecarga nuevas expresiones [] y eliminar [] y proporciona una función de plantilla para desasignar memoria y llamar al destructor para cada objeto si es necesario:
fuente
solo defina un destructor dentro de una clase y ejecute su código con ambas sintaxis
de acuerdo con la salida puedes encontrar las soluciones
fuente
La respuesta:
int * pArray = new int [5];
int tamaño = * (pArray-1);
Publicado anteriormente no es correcto y produce un valor no válido. El "-1" cuenta elementos En el sistema operativo Windows de 64 bits, el tamaño correcto del búfer reside en la dirección Ptr - 4 bytes
fuente