Pregunta

¿Es posible realmente hacer uso de la ubicación nueva en código portátil cuando se usa para matrices?

Parece que el puntero que obtienes de nuevo[] no siempre es el mismo que la dirección que ingresas (5.3.4, la nota 12 en el estándar parece confirmar que esto es correcto), pero no veo 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 produce 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;
}

Al observar la memoria, el compilador parece estar usando los primeros cuatro bytes del búfer para almacenar un recuento de la cantidad de elementos que contiene.Esto significa que debido a que el buffer es sólo sizeof(A)*NUMELEMENTS grande, el último elemento de la matriz se escribe en un montón no asignado.

Entonces, la pregunta es: ¿puede averiguar cuánta sobrecarga adicional desea su implementación para poder utilizar 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 las distintas clases.Por ejemplo, si elimino el destructor virtual en el ejemplo, la dirección devuelta por nuevo[] es la misma que la dirección que paso.

¿Fue útil?

Solución

Personalmente, elegiría la opción de no usar la ubicación nueva en la matriz y, en su lugar, usaría la ubicación nueva en cada elemento de la matriz individualmente.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 esos 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 instalado un compilador de C++).Todavía indica el punto :) ¡Espero que ayude de alguna manera!


Editar:

La razón por la que necesita realizar un seguimiento de la cantidad de elementos es para poder iterar a través de ellos cuando llama a eliminar en la matriz y asegurarse de que se llame a los destructores en cada uno de los objetos.Si no sabe cuántos hay, no podría hacer esto.

Otros consejos

@Derek

5.3.4, sección 12 habla sobre la sobrecarga de asignación de matrices y, a menos que lo esté malinterpretando, parece sugerirme que es válido que el compilador también lo agregue en la ubicación nueva:

Esta sobrecarga se puede aplicar en todas las nuevas expresiones de matriz, incluidas aquellas 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 gastos generales puede variar de una invocación de nuevo a otra.

Dicho esto, creo que VC fue el único compilador que me dio problemas con esto, aparte de GCC, Codewarrior y ProDG.Aunque tendría que volver a comprobarlo para estar seguro.

Gracias por las respuestas.Usar la ubicación nueva para cada elemento de la matriz fue la solución que terminé usando cuando me encontré 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 ubicación nueva [].Tal como están las cosas, parece que la ubicación new[] es esencialmente inutilizable gracias al estándar que permite al compilador agregar una sobrecarga adicional no especificada a la matriz.No veo cómo podrías usarlo de forma segura y portátil.

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

@Jaime

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

Después de pensarlo un poco, estoy de acuerdo contigo.No hay ninguna razón por la que la ubicación nueva deba almacenar la cantidad de elementos, porque no hay eliminación de ubicación.Dado que no se elimina ninguna ubicación, no hay motivo para que la ubicación nueva almacene la cantidad de elementos.

También probé 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 se trata de un problema de VC++ y si esto podría violar el estándar (hasta donde puedo encontrar, el estándar no aborda esto específicamente).

La ubicación de new en sí misma es portátil, pero las suposiciones que usted hace sobre lo que hace con un bloque de memoria específico no son portátiles.Como se dijo antes, si usted fuera un compilador y tuviera una porción de memoria, ¿cómo sabría cómo asignar una matriz y destruir adecuadamente cada elemento si todo lo que tuviera fuera un puntero?(Consulte la interfaz del operador eliminar [].)

Editar:

Y en realidad hay una eliminación de ubicación, solo que solo se llama cuando un constructor lanza una excepción al asignar una matriz con ubicación nueva [].

Si new[] realmente necesita realizar un seguimiento del número de elementos de alguna manera es algo que queda en manos del estándar, lo que lo deja en manos del compilador.Lamentablemente, en este caso.

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

Creo que puedes solucionar el problema cuando NUMELEMENTS es de hecho una constante de tiempo de compilación, así:

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

Esto debería utilizar la ubicación escalar nueva.

De manera similar a cómo usaría un solo elemento para calcular el tamaño de 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 en los que es posible que no se conozca el número de elementos, puede utilizar sizeof(A[1]) y multiplicarlo por el recuento de elementos requerido.

p.ej

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

for(int i = 0; i < NUMELEMENTS; ++i)
{
    pA[i] = new (pA + i) A();
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top