¿Cómo puede esta estructura tener un tamaño de == 0?

80

Hay una publicación antigua que solicita una construcción por la que sizeofregresaría 0. Hay algunas respuestas de alta puntuación de usuarios de alta reputación que dicen que, según el estándar, ningún tipo o variable puede tener un tamaño de 0. Y estoy 100% de acuerdo con eso.

Sin embargo, existe esta nueva respuesta que presenta esta solución:

struct ZeroMemory {
    int *a[0];
};

Estaba a punto de rechazarlo y comentarlo, pero el tiempo que pasé aquí me enseñó a verificar incluso las cosas de las que estoy 100% seguro. Así que ... para mi sorpresa tanto gccy clangmostrar los mismos resultados: sizeof(ZeroMemory) == 0. Aún más, el tamaño de una variable es 0:

ZeroMemory z{};
static_assert(sizeof(z) == 0); // Awkward...

¿Qué ...?

Enlace Godbolt

¿Cómo es esto posible?

bolov
fuente
37
La matriz de tamaño cero no es C ++ estándar. sino extensión.
Jarod42
@ Jarod42 ahora es obvio.
bolov
1
Además, no se compila bajo
MSVC
7
Ahora colóquelos en una matriz y haga aritmética de punteros sobre ellos.
CodesInChaos
Hmmm ... ¿Cuántos bytes no debería tomar nada si no es 0?
Gerhardh

Respuestas:

52

Antes de que se estandarizara C, muchos compiladores no habrían tenido dificultades para manejar tipos de tamaño cero siempre que el código nunca intentara restar un puntero a un tipo de tamaño cero de otro. Tales tipos eran útiles y apoyarlos era más fácil y barato que prohibirlos. Sin embargo, otros compiladores decidieron prohibir estos tipos, y algunos códigos de aserción estática pueden haberse basado en el hecho de que chillarían si el código intentara crear una matriz de tamaño cero. Los autores de la Norma se enfrentaron a una elección:

  1. Permitir que los compiladores acepten silenciosamente declaraciones de arreglos de tamaño cero, incluso en los casos en que el propósito de tales declaraciones sea desencadenar un diagnóstico y abortar la compilación, y requiera que todos los compiladores acepten tales declaraciones (aunque no necesariamente en silencio) como produciendo objetos de tamaño cero. .

  2. Permita que los compiladores acepten silenciosamente declaraciones de arreglos de tamaño cero, incluso en los casos en que el propósito de tales declaraciones sea desencadenar un diagnóstico y abortar la compilación, y permitir que los compiladores que encuentren tales declaraciones cancelen la compilación o la continúen a su gusto.

  3. Exija que las implementaciones emitan un diagnóstico si el código declara una matriz de tamaño cero, pero luego permita que las implementaciones cancelen la compilación o la continúen (con la semántica que consideren adecuada) a su gusto.

Los autores del Estándar optaron por el # 3. En consecuencia, las declaraciones de arreglos de tamaño cero son consideradas por la "extensión" del Estándar, a pesar de que tales construcciones fueron ampliamente soportadas antes de que el Estándar las prohibiera.

El estándar C ++ permite la existencia de objetos vacíos, pero en un esfuerzo por permitir que las direcciones de los objetos vacíos se puedan usar como tokens, exige que tengan un tamaño mínimo de 1. Para que un objeto que no tiene miembros tenga un tamaño de Por tanto, 0 violaría la Norma. Sin embargo, si un objeto contiene miembros de tamaño cero, el estándar C ++ no impone requisitos sobre cómo se procesa más allá del hecho de que un programa que contenga dicha declaración debe desencadenar un diagnóstico. Dado que la mayoría del código que usa tales declaraciones espera que los objetos resultantes tengan un tamaño de cero, el comportamiento más útil para los compiladores que reciben dicho código es tratarlos de esa manera.

Super gato
fuente
"Antes de que C se estandarizara", ¿quiere decir antes de que existiera C99?
Stargateur
1
@Stargateur: Me refiero a los aproximadamente 15 años entre la invención de C y la redacción del estándar C89. C99 volvió a agregar una forma limitada de objeto de tamaño cero para evitar algunos de los errores necesarios para evitar la prohibición de las matrices de tamaño cero, pero tales errores no habrían sido necesarios si C89 no hubiera prohibido molestamente las matrices de tamaño cero en El primer lugar.
supercat
"Estos tipos fueron útiles" ¿Cómo fueron útiles?
user109923
1
@ user109923: Como un ejemplo: struct polygon { int count; POINT sides[0];}; struct { struct polygon poly; POINT pts[3]; } myTriangle = { {3}, {{1,1},{2,2},{2,1} };. Habría que tener cuidado para asegurarse de que la alineación no causa problemas, pero se pueden tener objetos de duración estática de diferentes tamaños que se pueden procesar indistintamente.
supercat
42

Como señaló Jarod42, las matrices de tamaño cero no son C ++ estándar, sino extensiones GCC y Clang.

Agregar -pedanticproduce esta advertencia:

5 : <source>:5:12: warning: zero size arrays are an extension [-Wzero-length-array]
    int *a[0];
           ^

Siempre olvido que std=c++XX(en lugar de std=gnu++XX) no desactiva todas las extensiones.

Esto todavía no explica el sizeofcomportamiento. Pero al menos sabemos que no es estándar ...

bolov
fuente
1
De cualquier manera, es sorprendente ya que incluso una estructura vacía debería producir un valor de tamaño distinto de cero ...
WF
2
Curiosamente, si crea dos instancias automáticas, se almacenan sizeof(int*)aparte (supongo): coliru.stacked-crooked.com/a/e9a3038bf587b65c
eerorika
9
Esto solo responde que la matriz de tamaño cero no es estándar, pero no explica la pregunta que hizo.
haccks
1
El comportamiento no tiene que tener sentido de ninguna manera de acuerdo con el estándar, solo debe tener sentido para los implementadores del compilador. El compilador ya permite definir un tipo de una manera prohibida por el estándar. Como tal, los requisitos de la norma sobre lo que da el resultado sizeofpara ese tipo tampoco se aplican. Por lo tanto, el comportamiento es una decisión tomada por los desarrolladores del compilador.
Peter
1
La idea es ZeroMemory* z = (ZeroMemory*)malloc(sizeof(ZeroMemory) + 2 * sizeof(int*)); z.a[1] = new int{1}; /* or whatever, just use indices into a to access dynamic memory after the previous members (in this case: none) */, por supuesto, ¡esto no es compatible con el estándar! (Esto se puede utilizar para analizar fácilmente mensajes de red de longitud dinámica mediante la conversión a una estructura, por ejemplo).
hoffmale
19

En C ++, una matriz de tamaño cero es ilegal.

ISO / IEC 14882: 2003 8.3.4 / 1:

[..] Si la expresión-constante (5.19) está presente, será una expresión constante integral y su valor será mayor que cero . La expresión constante especifica el límite de (número de elementos en) la matriz. Si el valor de la expresión constante es N, la matriz tiene Nelementos numerados 0hasta N-1, y el tipo de identificador de Des " matriz de lista de tipos de declaradores derivados de NT". [..]

g ++ requiere que la -pedanticbandera dé una advertencia en una matriz de tamaño cero.

msc
fuente
5

Las matrices de longitud cero son una extensión de GCC y Clang. La aplicación sizeofa matrices de longitud cero se evalúa como cero .

Una clase de C ++ (vacía) no puede tener tamaño 0, pero tenga en cuenta que la clase ZeroMemoryno está vacía. Tiene un miembro con nombre con tamaño 0y la aplicación sizeofdevolverá cero.

haccks
fuente
5
entonces ... una clase vacía no puede tener tamaño 0, pero una clase no vacía puede tener tamaño 0... esto no tiene mucho sentido. Pero supongo que este es el tipo de caso de borde conflictivo que se obtiene con las extensiones no estándar.
bolov
@bolov; Una clase de c ++ (vacía) no puede tener un tamaño 0 según el estándar para una clase vacía. El estándar C ++ dice eso porque no hay otra forma de hacer una estructura de tamaño 0. En este caso particular, el miembro de la estructura en sí es de tamaño 0 y esto hace que el tamaño de la estructura sea 0. Sé que es confuso, pero hice todo lo posible para explicarlo.
haccks
3
@bodov: Pero esta no es una clase C ++, es una clase G ++, por lo que las reglas de C ++ no tienen que aplicarse a ella. En particular, tenga en cuenta que no puede tener una matriz de este tipo o hacer cálculos matemáticos con sus punteros, por lo que el razonamiento habitual de que el tamaño no puede ser cero tampoco es válido.
Ben Voigt