C ++ new int [0]: ¿asignará memoria?


241

Una aplicación de prueba simple:

cout << new int[0] << endl;

salidas:

0x876c0b8

Entonces parece que funciona. ¿Qué dice la norma sobre esto? ¿Es siempre legal "asignar" un bloque de memoria vacío?


44
+1 Pregunta muy interesante, aunque no estoy seguro de cuánto importa en código real.
Zifre el

37
@Zifre: pido curiosidad, pero podría importar en el mundo real, por ejemplo, cuando el tamaño de los bloques de memoria asignados se calcula de alguna manera, y el resultado del cálculo puede ser cero, entonces no hay necesidad directa de agregar excepciones para no asignar bloques de tamaño cero. Porque deberían asignarse y eliminarse sin errores (si solo el bloque de tamaño cero no está desreferenciado). Así que, en general, esto proporciona una abstracción más amplia de lo que es un bloque de memoria.

2
@ emg-2: en su situación de ejemplo, en realidad no importaría, porque eliminar [] es perfectamente legal en un puntero NULL :-).
Evan Teran el

2
Solo está relacionado tangencialmente, así que estoy comentando aquí, pero C ++ de muchas maneras garantiza que los objetos distintos tengan direcciones únicas ... incluso si no requieren almacenamiento explícitamente. Un experimento relacionado sería verificar el tamaño de una estructura vacía. O una matriz de esa estructura.
Drew Dormann el

2
Para elaborar el comentario de Shmoopty: especialmente cuando se programa con plantillas (por ejemplo, plantillas de clase de política como std :: allocator), es común en C ++ tener objetos de tamaño cero. Es posible que el código genérico deba asignar dinámicamente dichos objetos y utilizar punteros a ellos para comparar la identidad del objeto. Esta es la razón por la cual el operador new () devuelve punteros únicos para solicitudes de tamaño cero. Aunque podría decirse que es menos importante / común, el mismo razonamiento se aplica a la asignación de matriz y al operador new [] ().
Trevor Robinson

Respuestas:


234

Desde 5.3.4 / 7

Cuando el valor de la expresión en un declarador directo nuevo es cero, se llama a la función de asignación para asignar una matriz sin elementos.

Desde 3.7.3.1/2

El efecto de desreferenciar un puntero devuelto como una solicitud de tamaño cero no está definido.

también

Incluso si el tamaño del espacio solicitado [por nuevo] es cero, la solicitud puede fallar.

Eso significa que puede hacerlo, pero no puede desreferenciar legalmente (de una manera bien definida en todas las plataformas) la memoria que obtiene, solo puede pasarla a la eliminación de matriz, y debe eliminarla.

Aquí hay una nota interesante (es decir, no es una parte normativa de la norma, pero se incluye con fines expositivos) adjunta a la oración de 3.7.3.1/2

[32. La intención es que el operador new () sea implementable llamando a malloc () o calloc (), por lo que las reglas son sustancialmente las mismas. C ++ difiere de C al requerir una solicitud cero para devolver un puntero no nulo.]


Tengo una pérdida de memoria si no elimino. ¿Se espera? Al menos no me lo esperaba.
EralpB

12
@EralpB: por el contrario, incluso si su solicitud es cero, esta asignación se realiza en el montón, donde una solicitud implica varias operaciones de contabilidad, como la asignación e inicialización de guardias de montón antes y después de la zona dada por el asignador, inserción en listas libres o otras estructuras complejas horribles. Liberarlo significa hacer la contabilidad al revés.
v.oddou

3
@EralpB sí, supongo que puede esperar una pérdida de memoria cada vez que no equilibra un new[]con un delete[]- cualquiera sea el tamaño. En particular, cuando llama new[i], necesita un poco más de memoria que la que está tratando de asignar para almacenar el tamaño de la matriz (que luego se utiliza delete[]cuando se desasigna)
pqnet

24

Sí, es legal asignar una matriz de tamaño cero como esta. Pero también debes eliminarlo.


1
¿Tienes una cita para esto? Todos sabemos que int ar[0];es ilegal, ¿por qué está bien nuevo?
Motti el

44
Es interesante que C ++ no sea tan fuerte con la prohibición de objetos de tamaño cero. Piense en la optimización de la clase base vacía: aquí también, un subobjeto de clase base vacío puede tener un tamaño cero. En contraste, el Estándar C hace un gran esfuerzo para garantizar que nunca se creen objetos de tamaño cero: al definir malloc (0) dice que el efecto es ejecutar malloc con algún argumento distinto de cero, por ejemplo. Y en struct {...; T n []; }; cuando no hay espacio asignado para la matriz (FAM), dice que se comporta como si "n" tuviera un elemento. (En ambos casos, usar el objeto de cualquier manera es UB, como xn [0])
Johannes Schaub - litb

1
Creo que sizeof (type)se espera que nunca devuelva cero. Ver por ejemplo: stackoverflow.com/questions/2632021/can-sizeof-return-0-zero
pqnet

Incluso la llamada "optimización de clase base vacía" solo es relevante debido a la insistencia de que todos los objetos (a diferencia de todos los bytes dentro de los objetos) deben tener direcciones únicas. C ++ podría haberse simplificado si se requiriera un código que realmente se preocupara por los objetos con direcciones únicas para garantizar que tuvieran un tamaño distinto de cero.
supercat

15

¿Qué dice la norma sobre esto? ¿Es siempre legal "asignar" un bloque de memoria vacío?

Cada objeto tiene una identidad única, es decir, una dirección única, lo que implica una longitud distinta de cero (la cantidad real de memoria aumentará silenciosamente, si solicita cero bytes).

Si asignó más de uno de estos objetos, encontrará que tienen direcciones diferentes.


"Cada objeto tiene una identidad única, es decir, una dirección única, lo que implica una longitud distinta de cero": verdadero, pero incorrecto. El objeto tiene dirección, pero el puntero al objeto puede apuntar a memoria aleatoria. "La cantidad real de memoria aumentará silenciosamente, si solicita cero bytes" - no estoy seguro. operator []también almacena el tamaño de la matriz en algún lugar (ver isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array ). Entonces, si la implementación asigna el recuento de bytes con los datos, simplemente podría asignar el recuento de bytes y 0 bytes para los datos, devolviendo el último puntero.
Steed

Una longitud de memoria asignada distinta de cero, no una longitud de memoria utilizable distinta de cero. Mi punto era que dos punteros a dos objetos distintos no deberían apuntar a la misma dirección.
ChrisW

1
El estándar ( open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ) sí dice (3.7.4.1/2) que diferentes llamadas a operator new[]deberían devolver diferentes punteros. Por cierto, el new expressiontiene algunas reglas adicionales (5.3.4). No pude encontrar ninguna pista de que newcon el tamaño 0 se requiera para asignar algo. Lo siento, rechacé porque encuentro que su respuesta no responde las preguntas, sino que proporciona algunas declaraciones controvertidas.
Steed

@ La velocidad de la memoria para almacenar la longitud del bloque se puede calcular implícitamente utilizando el espacio de direcciones. Para matrices de tamaño cero, por ejemplo, podría ser posible que la new[]implementación devuelva direcciones en un rango donde no hay memoria dinámica asignada, por lo tanto, realmente no usa ninguna memoria (mientras usa el espacio de direcciones)
pqnet

@pqnet: Una implementación de buena calidad debería permitir un número ilimitado de ciclos nuevos / eliminar, ¿no es así? En una plataforma con punteros de 64 bits, podría ser razonable decir que una computadora que ejecuta un while(!exitRequested) { char *p = new char[0]; delete [] p; }bucle sin reciclar punteros colapsaría en polvo antes de que posiblemente se quedara sin espacio de direcciones, pero en una plataforma con punteros de 32 bits sería Suposición mucho menos razonable.
supercat

14

Sí, es completamente legal asignar un 0bloque de tamaño new. Simplemente no puede hacer nada útil con él ya que no hay datos válidos para acceder. int[0] = 5;es ilegal.

Sin embargo, creo que el estándar permite cosas como malloc(0)regresar NULL.

También necesitará delete []cualquier puntero que obtenga de la asignación también.


55
Con respecto a malloc, tiene razón: su implementación está definida. Esto a menudo se ve como una mala característica.

Supongo que una pregunta interesante es: ¿puede la versión nothrow de new return NULL si se le da un tamaño de 0?
Evan Teran el

1
La semántica de new y malloc no está vinculada de ninguna manera, al menos por el estándar.

sin decir que lo están ... estaba preguntando específicamente sobre la nueva versión de newhrow.
Evan Teran el

@Evan: la versión nothrow de los nuevos retornos es nula solo si la solicitud falla, no solo si el tamaño de la solicitud es 0 @Neil, no está vinculada normativamente, sino que está vinculada por intención (es decir, el operador nuevo puede implementarse en términos de malloc pero no el otro ) - vea la nota al pie que incluí en mi respuesta
Faisal Vali

1

Curiosamente, C ++ requiere que el operador nuevo devuelva un puntero legítimo incluso cuando se solicitan cero bytes. (Requerir este comportamiento de sonido extraño simplifica las cosas en otras partes del lenguaje).

Encontré que Efective C ++ Third Edition decía así en "Elemento 51: Adherirse a la convención al escribir nuevo y eliminar".


0

Le garantizo que el nuevo int [0] le cuesta espacio extra desde que lo probé.

Por ejemplo, el uso de memoria de

int **arr = new int*[1000000000];

es significativamente más pequeño que

int **arr = new int*[1000000000];
for(int i =0; i < 1000000000; i++) {
    arr[i]=new int[0];
}

El uso de memoria del segundo fragmento de código menos el del primer fragmento de código es la memoria utilizada para los numerosos nuevos int [0].

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.