¿La ubicación de la matriz nueva requiere una sobrecarga no especificada en el búfer?

Resuelto Mooing Duck asked hace 12 años • 7 respuestas

5.3.4 [expr.new]del borrador de C++ del 11 de febrero da el ejemplo:

new(2,f) T[5]resulta en una llamada de operator new[](sizeof(T)*5+y,2,f).

Aquí, xey son valores no negativos no especificados que representan la sobrecarga de asignación de matriz; el resultado de la nueva expresión se compensará con esta cantidad del valor devuelto por operator new[]. Esta sobrecarga se puede aplicar en todas las nuevas expresiones de matriz , incluidas aquellas que hacen referencia a la función de biblioteca operator 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. —fin del ejemplo ]

Ahora tome el siguiente código de ejemplo:

void* buffer = malloc(sizeof(std::string) * 10);
std::string* p = ::new (buffer) std::string[10];

Según la cita anterior, la segunda línea new (buffer) std::string[10]llamará internamente operator new[](sizeof(std::string) * 10 + y, buffer)(antes de construir los objetos individuales std::string). ¡El problema es que si y > 0, el búfer preasignado será demasiado pequeño!

Entonces, ¿cómo sé cuánta memoria debo preasignar cuando utilizo la ubicación de matriz nueva?

void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space);
std::string* p = ::new (buffer) std::string[10];

¿O el estándar en algún lugar garantiza eso y == 0en este caso? Nuevamente la cita dice:

Esta sobrecarga se puede aplicar en todas las nuevas expresiones de matriz , incluidas aquellas que hacen referencia a la función de biblioteca operator new[](std::size_t, void*)y otras funciones de asignación de ubicación.

Mooing Duck avatar Jan 04 '12 07:01 Mooing Duck
Aceptado

Actualizar

Nicol Bolas señala correctamente en los comentarios a continuación que esto se ha solucionado de modo que los gastos generales sean siempre cero paraoperator new[](std::size_t, void* p) .

Esta solución se realizó como informe de defectos en noviembre de 2019, lo que la hace retroactiva para todas las versiones de C++.

Respuesta original

No lo use operator new[](std::size_t, void* p)a menos que sepa a priori la respuesta a esta pregunta. La respuesta es un detalle de implementación y puede cambiar con el compilador/plataforma. Aunque suele ser estable para cualquier plataforma determinada. Por ejemplo, esto es algo especificado por Itanium ABI .

Si no sabe la respuesta a esta pregunta, escriba su propia matriz de ubicación nueva que pueda verificar esto en tiempo de ejecución:

inline
void*
operator new[](std::size_t n, void* p, std::size_t limit)
{
    if (n <= limit)
        std::cout << "life is good\n";
    else
        throw std::bad_alloc();
    return p;
}

int main()
{
    alignas(std::string) char buffer[100];
    std::string* p = new(buffer, sizeof(buffer)) std::string[3];
}

Al variar el tamaño de la matriz e inspeccionar nen el ejemplo anterior, puede inferir ypara su plataforma. Para mi plataforma y es 1 palabra. El tamaño de (palabra) varía dependiendo de si estoy compilando para una arquitectura de 32 o 64 bits.

Howard Hinnant avatar Jan 04 '2012 04:01 Howard Hinnant

Actualización: después de una discusión, entiendo que mi respuesta ya no se aplica a la pregunta. Lo dejaré aquí, pero definitivamente todavía se necesita una respuesta real.

Estaré encantado de apoyar esta pregunta con alguna recompensa si no se encuentra pronto una buena respuesta.

Replantearé la pregunta aquí según tengo entendido, con la esperanza de que una versión más breve pueda ayudar a otros a comprender lo que se pregunta. La pregunta es:

¿La siguiente construcción es siempre correcta? ¿Está arr == addral final?

void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N];                // #1

Sabemos por el estándar que el número 1 provoca la llamada ::operator new[](???, addr), donde ???es un número no especificado no menor que N * sizeof(T), y también sabemos que esa llamada solo regresa addry no tiene otros efectos. También sabemos que arrse compensa addrcorrespondientemente. Lo que no sabemos es si la memoria a la que apunta addres lo suficientemente grande o cómo sabríamos cuánta memoria asignar.


Pareces confundir algunas cosas:

  1. Tu ejemplo llama operator new[](), no operator new().

  2. Las funciones de asignación no construyen nada. Ellos asignan .

Lo que pasa es que la expresión T * p = new T[10]; provoca:

  1. una llamada a operator new[]()con argumento de tamaño 10 * sizeof(T) + x,

  2. diez llamadas al constructor predeterminado de T, efectivamente ::new (p + i) T().

La única peculiaridad es que la expresión array-new solicita más memoria de la que utilizan los propios datos de la matriz. Usted no ve nada de esto y no puede hacer uso de esta información de ninguna otra manera que no sea mediante la aceptación silenciosa.


Si tiene curiosidad sobre cuánta memoria se asignó realmente, simplemente puede reemplazar las funciones de asignación de matriz operator new[]y operator delete[]hacer que se imprima el tamaño real.


Actualización: como información aleatoria, debe tener en cuenta que las nuevas funciones de ubicación global deben ser no operativas. Es decir, cuando construyes un objeto o matriz en el lugar de esta manera:

T * p = ::new (buf1) T;
T * arr = ::new (buf10) T[10];

Luego las llamadas correspondientes a ::operator new(std::size_t, void*)y ::operator new[](std::size_t, void*)no hacen más que devolver su segundo argumento. Sin embargo, no sabes a qué buf10se supone que debe apuntar: necesita apuntar a 10 * sizeof(T) + ybytes de memoria, pero no puedes saberlo y.

Kerrek SB avatar Jan 04 '2012 00:01 Kerrek SB