Pregunta

Bien, creo que todos estamos de acuerdo en que lo que sucede con el código siguiente es indefinido, dependiendo de lo que se pasa,

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

El puntero puede ser todo tipo de cosas diferentes, y así realizar una incondicional delete[] en ella no está definido.Sin embargo, vamos a suponer 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, que es lo que sabe?Quiero decir que, desde el lenguaje/compilador punto de vista, no tiene idea de si o no arr es una matriz puntero frente a un puntero a una sola int.Heck, incluso no 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 sólo uno de int y no ir en algún tipo de 'matanza' eliminando el resto de la memoria más allá de ese punto (en contraste con el strlen y no\0cadena terminada -- se va a seguir hasta que llega a 0).

Así, cuyo trabajo es recordar estas cosas?¿El SO de mantener algún tipo de registro en el fondo?(Quiero decir, me doy cuenta de que he comenzado este post diciendo que lo que sucede no está definido, pero el hecho es que la 'matanza' escenario no sucede, por lo tanto, en el mundo práctico alguien es de recordar.)

¿Fue útil?

Solución

El compilador no sabe que es una matriz, que es confiar en el programador. La eliminación de un puntero a una sola int con delete [] daría como resultado un comportamiento indefinido. Su segundo ejemplo main() no es seguro, incluso si no se bloquea inmediatamente.

El compilador tiene que llevar un registro de la cantidad de objetos que ser eliminado de alguna manera. Se puede hacer esto mediante la sobre-asignación suficiente para almacenar el tamaño de la matriz. Para más detalles, consulte la C ++ FAQ Súper .

Otros consejos

Una pregunta que no parecen las respuestas dadas hasta ahora para abordar: si las bibliotecas de tiempo de ejecución (no el sistema operativo, en realidad) pueden realizar un seguimiento de la cantidad de cosas en la matriz, entonces ¿por qué necesitamos la sintaxis en delete[] ¿todas? ¿Por qué no puede una sola forma delete ser utilizado para manejar todas las eliminaciones?

La respuesta a esto se remonta a las raíces de C ++ s como un lenguaje compatible C-(que ya no realmente se esfuerza por ser.) La filosofía de BS era que el programador no debería tener que pagar por las características que no están utilizando. Si no están utilizando matrices, entonces no debería tener que asumir el coste de matrices de objetos para cada porción de memoria reservada de la memoria.

Es decir, si su código simplemente hace

Foo* foo = new Foo;

A continuación, el espacio de memoria que se asigna para foo no debe incluir ninguna sobrecarga adicional que sería necesaria para apoyar las matrices de Foo.

Dado que sólo las asignaciones de matriz están configurados para transportar la información adicional tamaño de la matriz, este caso es necesario contar las bibliotecas de tiempo de ejecución para buscar esa información cuando se eliminan los objetos. Es por eso que tenemos que utilizar

delete[] bar;

en lugar de sólo

delete bar;

Si bar es un puntero a una matriz.

Para la mayoría de nosotros (me incluyo), que fussiness unos pocos bytes adicionales de memoria parece pintoresco en estos días. Pero todavía hay algunas situaciones en las que el ahorro de unos pocos bytes (de lo que podría ser un muy alto número de bloques de memoria) puede ser importante.

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 4 primeros bytes de la memoria asignada y devolver un puntero de desplazamiento (es decir, asigna los espacios de memoria de 1000 a 1024 pero el puntero devuelto los puntos 1004, con lugares 1000-1003 almacenar el tamaño de la asignación).Entonces, al eliminar se llama, que se puede ver en 4 bytes antes de que el puntero del pasado para encontrar el tamaño de la asignación.

Estoy seguro de que hay otras maneras de realizar el seguimiento del tamaño de una distribución, pero esa es una opción.

Esto es muy similar a este pregunta y que tiene muchos de los detalles de su busca.

Sin embargo, basta con decir, no es el trabajo del sistema operativo para realizar un seguimiento de todo esto. En realidad las bibliotecas de tiempo de ejecución o el administrador de memoria subyacente que hará un seguimiento del tamaño de la matriz. Esto se hace generalmente mediante la asignación de memoria extra en la delantera y almacenar el tamaño de la matriz en ese lugar (la mayoría utiliza un nodo de cabeza).

Esto se puede ver en algunas implementaciones ejecutando el siguiente código

int* pArray = new int[5];
int size = *(pArray-1);

delete o delete[] probablemente tanto liberar 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, mezclando new/new[] y delete/delete[] es probablemente UB.

No sé que es una matriz, es por eso que tiene que suministrar delete[] en lugar de delete regulares de edad.

Yo tenía una pregunta similar a esta. En C, se puede asignar memoria con malloc () (u otra función similar), y eliminarlo con conexión (). Sólo hay una malloc (), que simplemente asigna un cierto número de bytes. Sólo hay un free (), que simplemente toma un puntero, ya que es el parámetro.

Por qué es que en C se puede Apenas entregue el puntero a liberar, pero en C ++ que debe indicarle si se trata de una matriz o una sola variable?

La respuesta, he aprendido, tiene que ver con los destructores de clase.

Si asigna una instancia de una clase MiClase ...

classes = new MyClass[3];

y eliminarlo con delete, sólo se puede obtener el destructor para la primera instancia de MyClass llamadas. Si utiliza delete [], puede estar seguro de que el destructor será llamado para todas las instancias de la matriz.

Esta es la diferencia importante. Si sólo va a trabajar con tipos estándar (por ejemplo, int) realmente no se vea este tema. Además, usted debe recordar que el comportamiento de eliminación en el uso de nueva [] y delete [] de nuevo es indefinido. - puede que no funcione de la misma manera en cada compilador / sistema

Es hasta el tiempo de ejecución, que es responsable de la asignación de memoria, de la misma manera que se puede eliminar una matriz creada con malloc en C estándar utilizando gratuita. Creo que cada compilador 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 es una matriz o un puntero, tiene que informar a él, y si se equivoca, o bien no elimina correctamente (Por ejemplo, en lugar de ptr gama ), o se termina teniendo un valor de relación para el tamaño y causar un daño significativo.

UNO DE LOS enfoques para compiladores es la asignación de un poco más de memoria y almacenar el número de elementos en el elemento head.

Ejemplo de cómo se podría hacer:Aquí

int* i = new int[4];

compilador asignará sizeof(int)*5 bytes.

int *temp = malloc(sizeof(int)*5)

Va a la tienda 4 en primer sizeof(int) bytes

*temp = 4;

y el conjunto de i

i = temp + 1;

Así i puntos a un array de 4 elementos, no 5.

Y

delete[] i;

serán procesados 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)

Semánticamente, ambas versiones de operador delete en C ++ puede "comer" cualquier puntero; sin embargo, si se le da un puntero a un solo objeto a delete[], entonces resultará UB, es decir, puede pasar cualquier cosa, incluyendo una caída del sistema o nada en absoluto.

C ++ requiere que el programador elegir la versión apropiada del operador delete dependiendo del sujeto de desasignación:. Array o un objeto único

Si el compilador puede determinar automáticamente si un puntero pasado al operador de eliminación fue una matriz de punteros, entonces no habría un solo operador delete en C ++, lo que sería suficiente para ambos casos.

De acuerdo en que el compilador no sabe si se trata de una matriz o no. Es responsabilidad del programador.

El compilador veces hacer un seguimiento de la cantidad de objetos que ser eliminado por un exceso de asignación suficiente para almacenar el tamaño de la matriz, pero no siempre es necesario.

Para una especificación completa cuando se asigna almacenamiento adicional, por favor refiérase a ABI C ++ (cómo se implementan los compiladores): Itanium C ++ ABI: array operador nuevas cookies

No se puede utilizar eliminar para una matriz, y no se puede utilizar delete [] para un no-matriz.

"comportamiento indefinido" simplemente significa la especificación de lenguaje no hace gaurantees en cuanto a lo que sucederá. No nessacerally significa que algo malo va a pasar.

  

Así que quién es el trabajo que hay que recordar estas cosas? ¿El OS mantener algún tipo de registro en el fondo? (Quiero decir, me doy cuenta que empecé este post diciendo que lo que sucede es indefinido, pero el hecho es que el escenario 'Asesinato múltiple' no sucede, por lo que, por tanto, en el mundo práctico alguien está recordando.)

En general, existen dos capas aquí. El administrador de memoria subyacente y la implementación en C ++.

En general, el administrador de memoria recordarán (entre otras cosas) el tamaño del bloque de memoria que se asignó. Esto puede ser más grande que el bloque de la implementación en C ++ pidió. Normalmente, el administrador de memoria almacena los metadatos que es antes de que el bloque asignado de memoria.

La aplicación C ++ por lo general sólo se recuerda el tamaño de la matriz si tiene que hacerlo por sus propios fines, por lo general debido a que el tipo tiene un destructor no trivial.

Así que para este tipo con un destructor trivial la aplicación de "eliminar" y "delete []" suele ser el mismo. La aplicación C ++ simplemente pasa el puntero al administrador de memoria subyacente. Algo así como

free(p)

Por otra parte en los modelos con un destructor no trivial "eliminar" y "delete []" son probablemente diferentes. "Eliminar" sería algo como (donde T es el tipo que el puntero apunta a)

p->~T();
free(p);

Mientras que "delete []" sería algo así como.

size_t * pcount = ((size_t *)p)-1;
size_t count = *count;
for (size_t i=0;i<count;i++) {
  p[i].~T();
}
char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
free(pmemblock);

Hey ho así que depende de lo que la asignación de nuevo con la expresión [] al asignar gama de acumulación de tipos o clases / estructura y que no proporciona su constructor y el destructor del operador de la tratará como un tamaño "sizeof ( objetos) * numObjects" en lugar de tabla de objeto, por tanto, en este caso número de objetos asignados no serán almacenados en cualquier lugar, sin embargo, si asigna tabla de objeto y le proporcionará constructor y el destructor en su objeto de un cambio de comportamiento, nueva expresión destinará 4 bytes cada vez almacenar el número de objetos en los primeros 4 bytes por lo que el destructor para cada uno de ellos puede ser llamado y por lo tanto nueva [] expresión volverá puntero desplazado por 4 bytes hacia adelante, que cuando se devuelve la memoria del delete [] expresión llamará a una plantilla de función primero, iterar a través de matriz de objetos y llamar a destructor para cada uno de ellos. He creado este sencillo código de bruja sobrecarga nueva [] y borrar las expresiones [] y proporciona una función de plantilla para cancelar la asignación de memoria y llamar a 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 );
}
///////////////////////////////////////////

acaba de definir un destructor dentro de una clase y ejecutar el código con las sintaxis

delete pointer

delete [] pointer

acuerdo con la salida u puede encontrar las soluciones

La respuesta:

int * pArray = new int [5];

Tamaño int = * (pArray-1);

Publicado anterior no es correcta y produce valor no válido.  El "-1" cuenta elementos En 64 bits del sistema operativo Windows el tamaño del búfer correcta reside en Ptr - 4 bytes de dirección

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top