2008-08-18 16 views
30

¿Es posible utilizar la ubicación nueva en el código portátil cuando se usa para arreglos?¿Se puede utilizar la colocación nueva para matrices de forma portátil?

Parece que el puntero que obtiene de nuevo [] no es siempre el mismo que la dirección que ingresa (5.3.4, la nota 12 en el estándar parece confirmar que esto es correcto), pero no lo hago Veamos cómo puede asignar un búfer para que entre la matriz si este es el caso.

El siguiente ejemplo muestra el problema. Compilado con Visual Studio, este ejemplo resulta en daños en la memoria:

#include <new> 
#include <stdio.h> 

class A 
{ 
    public: 

    A() : data(0) {} 
    virtual ~A() {} 
    int data; 
}; 

int main() 
{ 
    const int NUMELEMENTS=20; 

    char *pBuffer = new char[NUMELEMENTS*sizeof(A)]; 
    A *pA = new(pBuffer) A[NUMELEMENTS]; 

    // With VC++, pA will be four bytes higher than pBuffer 
    printf("Buffer address: %x, Array address: %x\n", pBuffer, pA); 

    // Debug runtime will assert here due to heap corruption 
    delete[] pBuffer; 

    return 0; 
} 

En cuanto a la memoria, el compilador parece estar usando los cuatro primeros bytes de la memoria intermedia para almacenar un recuento del número de elementos que contiene. Esto significa que debido a que el buffer es solo sizeof(A)*NUMELEMENTS grande, el último elemento de la matriz se escribe en el montón no asignado.

Entonces, la pregunta es ¿puede averiguar cuánto sobrecarga adicional quiere su implementación para poder usar la ubicación nueva [] de forma segura? Idealmente, necesito una técnica que sea portátil entre diferentes compiladores. Tenga en cuenta que, al menos en el caso de VC, la sobrecarga parece diferir para diferentes clases. Por ejemplo, si elimino el destructor virtual en el ejemplo, la dirección devuelta desde el nuevo [] es la misma que la dirección que paso.

+0

maldiciones ah.Hice una estafa de tu pregunta :([Colocación de array-new requiere sobrecarga no especificada en el búfer?] (Http://stackoverflow.com/questions/8720425/array-placement-new-requires-unspecified-overhead-in-the -buffer) –

+0

Hmm ... si la sobrecarga desaparece cuando elimina el destructor virtual, eso sugeriría que la sobrecarga es probable de la clase 'vtable o de la implementación de VStudio de RTTI. –

+0

O al menos parte de la sobrecarga es. También es posible que la sobrecarga solo se use si la clase tiene un destructor no trivial. –

Respuesta

22

Personalmente, me gustaría ir con la opción de no utilizar la ubicación nueva en la matriz y en su lugar utilizar la colocación nueva en cada elemento de la matriz de forma individual. Por ejemplo:

int main(int argc, char* argv[]) 
{ 
    const int NUMELEMENTS=20; 

    char *pBuffer = new char[NUMELEMENTS*sizeof(A)]; 
    A *pA = (A*)pBuffer; 

    for(int i = 0; i < NUMELEMENTS; ++i) 
    { 
    pA[i] = new (pA + i) A(); 
    } 

    printf("Buffer address: %x, Array address: %x\n", pBuffer, pA); 

    // dont forget to destroy! 
    for(int i = 0; i < NUMELEMENTS; ++i) 
    { 
    pA[i].~A(); 
    }  

    delete[] pBuffer; 

    return 0; 
} 

Independientemente del método que utilice, asegúrese de destruir manualmente cada uno de los elementos de la matriz antes de eliminar pBuffer, ya que podría terminar con fugas;)

Nota : No he compilado esto, pero creo que debería funcionar (estoy en una máquina que no tiene un compilador de C++ instalado). Todavía indica el punto :) Espero que ayude de alguna manera!


Editar:

La razón por la que necesita para realizar un seguimiento del número de elementos es para que pueda recorrer a través de ellos cuando se llama a borrar en la matriz y asegúrese de que los destructores son llamados en cada una de los objetos. Si no sabe cuántos hay, no podría hacer esto.

+1

VC++ es un compilador incorrecto. La nueva matriz de ubicación predeterminada también conocida como 'new (void *) type [n]' realiza la construcción in situ de objetos 'n' de 'tipo'. El puntero proporcionado debe alinearse correctamente para que coincida 'alignof (type)' (note: 'sizeof (type)' es un múltiplo de 'alignof (type)' debido al relleno). Debido a que normalmente tendrías que transportar la longitud de la matriz, no existe un requisito real para almacenarla dentro de la matriz, ya que vas a destruirla con un bucle for for all (sin operadores de eliminación de ubicación). – bit2shift

+1

@ bit2shift El estándar de C++ establece explícitamente que 'new []' rellena la memoria asignada, aunque permite un relleno de 0 bytes. (Consulte la sección "expr.new", específicamente los ejemplos (14.3) y (14.4) y la explicación debajo de ellos). Por lo tanto, en ese sentido, en realidad cumple con los estándares. \ [Si no tiene una copia de la versión final del estándar C++ 14, consulte la página 133 [aquí] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers /2014/n4296.pdf). \] –

+1

@JustinTime Ese es un defecto que nadie parece vigilar. – bit2shift

1

Creo que gcc hace lo mismo que MSVC, pero por supuesto esto no t hacerlo "portátil".

creo que puede solucionar el problema cuando numElements es de hecho un tiempo de compilación constante, así:

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

Esto debería utilizar la colocación escalar nuevas.

+1

No funciona. 'operator new()' vs 'operator new []()' depende de si hay una matriz tipo involucrado, no si había '[]' en el código fuente –

1

De forma similar a como usaría un solo elemento para calcular el tamaño para una ubicación nueva, use una matriz de esos elementos para calcular el tamaño requerido para una matriz.

Si necesita el tamaño para otros cálculos donde no se conoce el número de elementos, puede usar sizeof (A [1]) y multiplicar por el número de elementos requeridos.

e.g

char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ]; 
A *pA = (A*)pBuffer; 

for(int i = 0; i < NUMELEMENTS; ++i) 
{ 
    pA[i] = new (pA + i) A(); 
} 
+0

El punto es que MSVC aparentemente requiere espacio adicional más allá del valor de 'sizeof (A [NUMELEMENTS])' en el caso de 'new []'. 'sizeof (A [N])' va a ser 'N * sizeof (N)' en general y no reflejará este espacio adicional requerido. – BeeOnRope

1

Gracias por las respuestas. Usar la ubicación nueva para cada elemento en la matriz fue la solución que terminé usando cuando me topé con esto (lo siento, debería haberlo mencionado en la pregunta). Simplemente sentí que debía haber algo que me faltaba al hacerlo con la colocación nueva []. Tal como está, parece que la colocación nueva [] es esencialmente inutilizable gracias al estándar que permite al compilador añadir una sobrecarga adicional no especificada a la matriz. No veo cómo podrías usarlo de manera segura y portátil.

Ni siquiera estoy realmente claro por qué necesita los datos adicionales, ya que no llamaría a eliminar [] en la matriz de todos modos, así que no veo por qué necesita saber cuántos elementos hay en ella .

2

@ James

Ni siquiera soy muy claro por qué necesita los datos adicionales, ya que no sería llamar a delete [] en la matriz de todos modos, así que no veo del todo por lo que necesita para saber cuántos artículos hay en él.

Después de pensar en esto, estoy de acuerdo contigo. No hay ninguna razón por la cual la ubicación nueva deba almacenar la cantidad de elementos, porque no hay eliminación de ubicación. Como no hay eliminación de ubicación, no hay ninguna razón para la colocación nueva para almacenar la cantidad de elementos.

También he probado esto con gcc en mi Mac, usando una clase con un destructor. En mi sistema, la ubicación nueva era no cambiando el puntero. Esto me hace preguntarme si esto es un problema de VC++, y si esto podría violar el estándar (el estándar no aborda específicamente esto, hasta donde puedo encontrar).

+0

Probé con ambos clang 3.7.0 y GCC 5.3.0, ambos con '-std = C++ 14' y' -pedantic' en [Coliru] (http://coliru.stacked-crooked.com). Ninguno mostró la existencia de una sobrecarga, especialmente con clases con destructores no triviales. Entonces, estoy pensando, este es otro ejemplo de cómo Visual C++ es un compilador tan malo. – bit2shift

4

@Derek

5.3.4, el artículo 12 habla de la sobrecarga de asignación de matriz y, a menos que esté leyendo mal, parece sugerir a mí que es válido para el compilador para agregarlo en la colocación de nuevo también:

Esta sobrecarga puede aplicarse en todas las expresiones nuevas de matriz, incluidas las que hacen referencia al operador de función de biblioteca new [] (std :: size_t, void *) y otras funciones de asignación de ubicación. La cantidad de sobrecarga puede variar de una invocación de nueva a otra.

Dicho esto, creo VC fue el único compilador que me dio problemas con esto, fuera de él, GCC, y Codewarrior ProDG. Sin embargo, tendría que volver a verificar para estar seguro.

+0

Me sorprendería si VC fuera el único compilador que agrega espacio adicional, creo que todos los compiladores almacenarían la cantidad de destructores para llamar allí. No hay otro lugar lógico para expresarlo. –

+1

@MooingDuck no hay operador de eliminación de ubicación para hacer uso de la "longitud de matriz" antes mencionada. De hecho, tiene que llamar manualmente a los destructores para una matriz ** construida ** con 'nuevo (puntero) tipo [longitud]'. VC es un mal compilador, todos deberían saberlo. – bit2shift

3

Colocación nueva en sí es portátil, pero los supuestos que tome acerca de lo que hace con un bloque de memoria especificada no son portátiles. Al igual que lo que se dijo antes, si fuera un compilador y se le diera un trozo de memoria, ¿cómo sabría cómo asignar una matriz y destruir adecuadamente cada elemento si todo lo que tuviera fuera un puntero? (Ver la interfaz del operador delete [].)

Editar:

Y hay realmente eliminar una ubicación, sólo que sólo se invoca cuando un constructor lanza una excepción, mientras que la asignación de una matriz con la colocación de nueva [].

Ya sea nuevo [] realmente necesita para realizar un seguimiento del número de elementos de alguna manera es algo que se deja a la norma, lo que deja en manos del compilador. Lamentablemente, en este caso.

+1

¿Cómo puede ser "portátil" si sobrescribe una cantidad arbitrariamente grande de almacenamiento? Nunca se puede llamar con seguridad, ya que nunca se sabe cuánto va a escribir. – BeeOnRope

+0

La expresión 'delete []' necesita saber, pero solo puede usarse en el resultado de una expresión 'new []' con asignación de montón (throwing o 'nothrow'). Para la colocación 'new []' es realmente innecesario.La única explicación que tengo para el comportamiento de VC es que el montón 'new []' se implementa de alguna manera en términos de la colocación 'new []' y no almacena la cantidad de elementos. –

Cuestiones relacionadas