2008-10-31 31 views
23

Esta es una pregunta que me ha estado molestando durante algún tiempo. Siempre pensé que C++ debería haber sido diseñado para que el operador "eliminar" (sin paréntesis) funcione incluso con el operador "nuevo []".¿Por qué incluso necesitamos el operador "delete []"?

En mi opinión, escribiendo esto:

int* p = new int; 

debe ser equivalente a la asignación de una matriz de 1 elemento:

int* p = new int[1]; 

Si esto fuera cierto, el operador "eliminar" siempre se pudo borrando arrays, y no necesitaríamos el operador "delete []".

¿Hay alguna razón por la cual el operador "delete []" se introdujo en C++? La única razón por la que puedo pensar es que la asignación de matrices tiene una pequeña huella de memoria (hay que almacenar el tamaño de la matriz en algún lugar), por lo que distinguir "eliminar" frente a "eliminar []" fue una pequeña optimización de la memoria.

Respuesta

32

Es así que se invocarán los destructores de los elementos individuales. Sí, para arreglos de POD, no hay mucha diferencia, pero en C++, puede tener matrices de objetos con destructores no triviales.

Ahora, la pregunta es, por qué no hacer new y delete se comportan como new[] y delete[] y deshacerse de new[] y delete[]? Volvería al libro "Diseño y evolución" de Stroustrup, donde dijo que si no usa las funciones de C++, no debería tener que pagarlas (en tiempo de ejecución al menos). Tal como está ahora, un new o delete se comportará tan eficientemente como malloc y free. Si delete tuviera el significado delete[], habría una sobrecarga adicional en el tiempo de ejecución (como señaló James Curran).

+4

Actualmente, cuando usa la nueva int [1], almacena en el encabezado de la matriz antes de que aparezcan los primeros datos, el tamaño de la misma. Por lo tanto, usar delete en lugar de delete [] no liberará esa parte de la memoria. – Rodrigo

2

delete [] asegura que se llama al destructor de cada miembro (si corresponde para el tipo) mientras que delete simplemente elimina la memoria asignada para la matriz.

Aquí es una buena lectura: http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=287

Y no, tamaños de matriz no se almacenan en cualquier lugar en C++. (Gracias a todos por señalar que esta afirmación es inexacta)

+0

No está de acuerdo con su última declaración. El compilador tiene que conocer el tamaño de la matriz para llamar al destructor para cada objeto en la matriz. Creo que estás confundiendo esto con el hecho de que C++ no hace límites, verificando matrices. –

+0

Oh cierto. Pensé que sugerías que el tamaño se almacenara como parte de la estructura de datos de la matriz (búfer). Sí, el compilador probablemente tenga que almacenar la información de tamaño en alguna parte ... –

+0

Un enfoque es almacenar el tamaño y el número de elementos en la palabra antes del inicio de la matriz. Esto se llama una cookie. – Dynite

3

Marshall Cline tiene info on this topic.

+0

Ese enlace no explica por qué el lenguaje fue diseñado para necesitar operadores separados 'delete' y' delete [] '. – jamesdlin

6

Dado que todos los demás parecen haber perdido el sentido de su pregunta, agregaré que tuve el mismo pensamiento hace algunos años, y nunca he podido obtener una respuesta.

La única cosa que puedo pensar es que hay una muy pequeña cantidad de sobrecarga adicional para el tratamiento de un solo objeto como una matriz (una innecesaria "for(int i=0; i<1; ++i)")

+1

Además de un poquito de memoria para almacenar el tamaño. –

+0

Sí, apostaría que la sobrecarga de la memoria se consideraba inaceptable. Posiblemente el lazo también lo era. –

8

Maldición, me perdí todo el sentido de la pregunta, pero Dejaré mi respuesta original como nota al margen. La razón por la que tenemos delete [] es porque hace mucho tiempo teníamos delete [cnt], incluso hoy si escribes delete [9] o delete [cnt], el compilador simplemente ignora la cosa entre [] pero compila ok. En ese momento, C++ fue procesado por un front-end y luego alimentado a un compilador de C ordinario. No podían hacer el truco de almacenar el conteo en algún lugar debajo de la cortina, tal vez ni siquiera podían pensar en eso en ese momento.Y para compatibilidad con versiones anteriores, los compiladores probablemente usaron el valor dado entre [] como el recuento de matriz, si no hay tal valor, obtuvieron el recuento del prefijo, por lo que funcionó en ambos sentidos. Más tarde, no escribimos nada entre [] y todo funcionó. Hoy, no creo que sea necesario "eliminar []", pero las implementaciones así lo exigen.

Mi respuesta original (que pierde el punto) ::

"Borrar" elimina un único objeto. "delete []" borra una matriz de objetos. Para que delete [] funcione, la implementación mantiene la cantidad de elementos en la matriz. Acabo de verificar esto al depurar el código ASM. En la implementación (VS2005) que probé, el recuento se almacenó como un prefijo para la matriz de objetos.

Si utiliza "eliminar []" en un solo objeto, la variable de conteo es basura por lo que el código falla. Si usa "eliminar" para una matriz de objetos, debido a alguna incoherencia, el código falla. ¡Probé estos casos justo ahora!

"eliminar simplemente borra la memoria asignada para la matriz". declaración en otra respuesta no es correcta. Si el objeto es una clase, delete llamará al DTOR. Simplemente coloque un punto de corte en el código DTOR y elimine el objeto, el punto de interrupción se activará.

Lo que se me ocurrió es que si las compilaciones & supusieron que todos los objetos asignados por "nuevo" son matrices de objetos, sería correcto llamar a "eliminar" para objetos individuales o matrices de objetos. objetos individuales simplemente sería el caso especial de una matriz de objetos que tiene un recuento de 1. Tal vez hay algo que me falta, de todos modos ...

5

La adición de este puesto que ninguna otra respuesta que actualmente se ocupa de:

matriz delete[] no se puede usar en una clase de puntero a base nunca - mientras el compilador almacena el recuento de objetos cuando invoca new[], no almacena los tipos o tamaños de los objetos (como señaló David, en C++ rara vez paga para una función que no estás usando). Sin embargo, escalar delete puede eliminar de forma segura a través de la clase base, por lo que se utiliza tanto para la limpieza normal y la limpieza de objetos polimórficos:

struct Base { virtual ~Base(); }; 
struct Derived : Base { }; 
int main(){ 
    Base* b = new Derived; 
    delete b; // this is good 

    Base* b = new Derived[2]; 
    delete[] b; // bad! undefined behavior 
} 

Sin embargo, en el caso contrario - destructor no virtual - escalar delete debería ser tan barato como sea posible, no debe verificar el número de objetos ni el tipo de objeto que se elimina. Esto hace que eliminar en un tipo integrado o llanura de edad-en datos de tipo muy barato, ya que el compilador sólo es necesario invocar ::operator delete y nada más:

int main(){ 
    int * p = new int; 
    delete p; // cheap operation, no dynamic dispatch, no conditional branching 
} 

Aunque no es un tratamiento exhaustivo de asignación de memoria, espero que esto ayude aclarar la amplitud de las opciones de administración de memoria disponibles en C++.

0

Estoy un poco confundido por la respuesta de Aaron y francamente admito que no entiendo completamente por qué y dónde es necesario eliminar [].

Hice algunos experimentos con su código de muestra (después de corregir algunos errores tipográficos). Aquí están mis resultados. Typos: ~ Base necesitaban un cuerpo de la función Base * b se declaró dos veces

struct Base { virtual ~Base(){ }>; }; 
struct Derived : Base { }; 
int main(){ 
Base* b = new Derived; 
delete b; // this is good 

<strike>Base</strike> b = new Derived[2]; 
delete[] b; // bad! undefined behavior 
} 

compilación y ejecución

[email protected]:g++ -o atest atest.cpp 
[email protected]: ./atest 
[email protected]: # No error message 

programa modificado con delete [] eliminado

struct Base { virtual ~Base(){}; }; 
struct Derived : Base { }; 

int main(){ 
    Base* b = new Derived; 
    delete b; // this is good 

    b = new Derived[2]; 
    delete b; // bad! undefined behavior 
} 

compilación y ejecución

[email protected]:g++ -o atest atest.cpp 
[email protected]: ./atest 
atest(30746) malloc: *** error for object 0x1099008c8: pointer being freed was n 
ot allocated 
*** set a breakpoint in malloc_error_break to debug 
Abort trap: 6 

Por supuesto, no sé si borrar [] b realmente está funcionando en el primer ejemplo; Solo sé que no da un mensaje de error del compilador.

Cuestiones relacionadas