Colocación de la matriz - nuevo requiere sobrecarga no especificada en el búfer?


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í, x e y son valores no negativos no especificados que representan la sobrecarga de asignación de matrices; el resultado de la nueva expresión se compensará con esta cantidad del valor devuelto por operator new[]. Esta sobrecarga se puede aplicar en todos los array new-expressions , incluyendo aquellos que hacen referencia a la función de biblioteca operator new[](std::size_t, void*) y otra asignación de colocación función. La cantidad de gastos generales puede variar de una invocación de nuevo a otro. - ejemplo final ]

Ahora tomemos el siguiente código de ejemplo:

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

De acuerdo con 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 preasignar cuando se utiliza matriz colocación-nuevo?

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

¿O la norma garantiza en algún lugar que y == 0 en este caso? De nuevo, la cita dice:

Esta sobrecarga se puede aplicar en todos los array new-expressions, incluyendo aquellos que hacen referencia a la función de biblioteca operator new[](std::size_t, void*) y otras funciones de asignación de colocación.

Author: R. Martinho Fernandes, 2012-01-04

6 answers

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

Si no conoce la respuesta a esta pregunta, escriba su propia matriz de colocació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];
}

Variando el tamaño de la matriz e inspeccionando n en el ejemplo anterior, puede inferir y para su plataforma. Para mi plataforma y es 1 palabra. El sizeof (word) varía dependiendo de si estoy compilando para una arquitectura de 32 bits o 64 bits.

 38
Author: Howard Hinnant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-05 02:07:07

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

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

Voy a replantear la pregunta aquí en la medida en que la entiendo, con la esperanza de que una versión más corta podría ayudar a otros a entender lo que se está preguntando. La pregunta es:

Es la siguiente construcción ¿siempre correcto? Es arr == addr al final?

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

Sabemos por el estándar que #1 causa 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 devuelve addr y no tiene otros efectos. También sabemos que arr se compensa de addr correspondientemente. Lo que sabemos no es si la memoria apuntada por addr es suficientemente grande, o cómo sabríamos cuánta memoria asignar.


Parece que confunde algunas cosas:

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

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

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

  1. Una llamada a operator new[]() con argumento size10 * sizeof(T) + x,

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

El único la peculiaridad es que la expresión array-new pide más memoria de la que utilizan los propios datos del array. 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 por saber cuánta memoria se asignó realmente, simplemente puede reemplazar las funciones de asignación de matrices operator new[] y operator delete[] y hacer que imprima el tamaño real.


Actualización: Como una pieza aleatoria de información, debe tener en cuenta que las funciones de colocación global-new deben ser no-ops. Es decir, cuando se construye un objeto o matriz en el lugar como así:

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

Entonces 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é buf10 se supone que apunta: Necesita apuntar a 10 * sizeof(T) + y bytes de memoria, pero no puedes saber y.

 7
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-02 13:08:52

Llamar a cualquier versión de operator new[] () no funcionará demasiado bien con un área de memoria de tamaño fijo. Esencialmente, se asume que delega a alguna función de asignación de memoria real en lugar de simplemente devolver un puntero a la memoria asignada. Si ya tiene una arena de memoria donde desea construir una matriz de objetos, desea usar std::uninitialized_fill() o std::uninitialized_copy() para construir los objetos (o alguna otra forma de construir individualmente los objetos).

Usted podría argumentar que esto significa que usted tiene que destruye los objetos en tu arena de memoria manualmente también. Sin embargo, llamar a delete[] array en el puntero devuelto desde la colocación new no funcionará: ¡usaría la versión sin colocación de operator delete[] ()! Es decir, cuando se utiliza la colocación new es necesario destruir manualmente el objeto(s) y liberar la memoria.

 6
Author: Dietmar Kühl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-04 05:58:38

Como menciona Kerrek SB en los comentarios, este defecto fue reportado por primera vez en 2004, y se resolvió en 2012 como:

El CWG acordó que el EWG es el lugar apropiado para tratar esta cuestión.

Entonces el defecto fue reportado a EWG en 2013, pero cerrado como NAD (presumiblemente significa "No es un defecto" ) con el comentario:

El problema está en intentar usar array new para poner un array en almacenamiento preexistente. No necesitamos usar array nuevo para eso; solo constrúyelos.

Lo que presumiblemente significa que la solución sugerida es usar un bucle con una llamada a la colocación no-matriz nueva una vez para cada objeto que se está construyendo.


Un corolario no mencionado en otra parte del hilo es que este código causa un comportamiento indefinido para todos T:

T *ptr = new T[N];
::operator delete[](ptr);

Incluso si cumplimos con las reglas de vida (es decir, T o bien tiene una destrucción trivial, o el programa no depende del destructor efectos secundarios), el problema es que ptr se ha ajustado para esta cookie no especificada, por lo que es el valor incorrecto para pasar a operator delete[].

 4
Author: M.M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-14 22:39:42

Después de leer las secciones estándar correspondientes estoy satarteando pensar que la colocación de nuevo para los tipos de matriz es simplemente una idea inútil, y la única razón para que esté permitida por el estándar es la forma genérica en la que se describe el operador nuevo:

La nueva expresión intenta crear un objeto del typeid (8.1) o newtypeid al que se aplica. El tipo de ese objeto es el tipo asignado. Este tipo será un tipo de objeto completo, pero no un clase abstracta tipo o matriz de los mismos (1.8, 3.9, 10.4). [Nota: porque las referencias no son objetos, las referencias no pueden ser creadas por newexpressions. ] [Nota: el typeid puede ser un tipo cvqualified, en en qué caso el objeto creado por newexpression tiene un cvqualified tipo. ]

new-expression: 
    ::(opt) new new-placement(opt) new-type-id new-initializer(opt)
    ::(opt) new new-placement(opt) ( type-id ) new-initializer(opt)

new-placement: ( expression-list )

newtypeid:
    type-specifier-seq new-declarator(opt)

new-declarator:
    ptr-operator new-declarator(opt)
    direct-new-declarator

direct-new-declarator:
    [ expression ]
    direct-new-declarator [ constant-expression ]

new-initializer: ( expression-list(opt) )

A mí me parece que array placement new simplemente se deriva de la compacidad de la definición (todos los usos posibles como un esquema), y parece que no hay una buena razón para que se prohíba.

Esto nos deja en una situación donde tenemos operador inútil, que necesita memoria asignada antes de que se sepa cuánto se necesitará. Las únicas soluciones que veo serían sobreasignar la memoria y esperar que el compilador no quiera más de lo suministrado, o reasignar la memoria en la función/método array placement new anulada (lo que más bien frustra el propósito de usar array placement new en primer lugar).


Para responder a la pregunta señalada por Kerrek SB: Su ejemplo:

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

No siempre es correcto. En la mayoría implementaciones arr!=addr (y hay buenas razones para ello) por lo que su código no es válido, y su búfer será invadido.

Acerca de esas "buenas razones" - tenga en cuenta que los creadores estándar lo liberan de algún tipo de mantenimiento de la casa cuando usa el operador array new, y array placement new no es diferente en este sentido. Tenga en cuenta que no necesita informar a delete[] sobre la longitud de la matriz, por lo que esta información debe mantenerse en la matriz misma. ¿En dónde? Exactamente en esta memoria extra. Sin ella delete[] ' ing requeriría mantener la longitud de la matriz separada (como lo hace stl usando bucles y sin colocación new)

 1
Author: j_kubik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-04 22:54:54

Esta sobrecarga se puede aplicar en todos los array new-expressions, incluyendo aquellos que hacen referencia a la función de biblioteca operator new[](std::size_t, void*) y otras funciones de asignación de colocación.

Este es un defecto en el estándar. Se rumorea que no pudieron encontrar un voluntario para escribir una excepción a ella (Mensaje #1165).

El colocación de matriz no reemplazable-nuevo no se puede utilizar con delete[] expresiones, por lo que necesita bucle a través de la array y llama a cada destructor .

La sobrecarga se dirige a la ubicación de la matriz definida por el usuario -nuevas funciones, que asignan memoria al igual que la T* tp = new T[length] normal. Estos son compatibles con delete[], de ahí la sobrecarga que lleva la longitud de la matriz.

 0
Author: bit2shift,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-26 03:51:51