¿Qué sucede si defino una matriz de tamaño 0 en C / C ++?

127

Por curiosidad, ¿qué sucede realmente si defino una matriz de longitud cero int array[0];en el código? GCC no se queja en absoluto.

Programa de muestra

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

Aclaración

Realmente estoy tratando de averiguar si las matrices de longitud cero inicializadas de esta manera, en lugar de señalarlas como la longitud variable en los comentarios de Darhazer, están optimizadas o no.

Esto se debe a que tengo que liberar algo de código en la naturaleza, así que estoy tratando de averiguar si tengo que manejar los casos en los que SIZEse define como 0, lo que sucede en algunos códigos con una definición estáticaint array[SIZE];

De hecho, me sorprendió que GCC no se quejara, lo que llevó a mi pregunta. De las respuestas que he recibido, creo que la falta de una advertencia se debe en gran medida al soporte de código antiguo que no se ha actualizado con la nueva sintaxis [].

Debido a que principalmente me preguntaba sobre el error, estoy etiquetando la respuesta de Lundin como correcta (la de Nawaz fue la primera, pero no fue tan completa); los otros señalaron su uso real para estructuras acolchadas con cola, aunque relevante, no es Es exactamente lo que estaba buscando.

Alex Koay
fuente
51
@AlexanderCorwin: Desafortunadamente en C ++, con un comportamiento indefinido, extensiones no estándar y otras anomalías, probar algo usted mismo a menudo no es un camino hacia el conocimiento.
Benjamin Lindley
55
@JustinKirk También me quedé atrapado por eso al probar y ver que funcionó. Y debido a las críticas que recibí en mi publicación, aprendí que probarlo y verlo funcionar no significa que sea válido y legal. Entonces, una autocomprobación a veces no es válida.
StormByte
2
@JustinKirk, vea la respuesta de Matthieu para ver un ejemplo de dónde lo usaría. También puede ser útil en una plantilla donde el tamaño de la matriz es un parámetro de plantilla. El ejemplo en la pregunta obviamente está fuera de contexto.
Mark Ransom
2
@JustinKirk: ¿Cuál es el propósito de []Python o incluso ""de C? A veces, tienes una función o una macro que requiere una matriz, pero no tienes ningún dato para agregar.
dan04
15
¿Qué es "C / C ++"? Estos son dos idiomas separados
carreras de ligereza en órbita

Respuestas:

86

Una matriz no puede tener tamaño cero.

ISO 9899: 2011 6.7.6.2:

Si la expresión es una expresión constante, tendrá un valor mayor que cero.

El texto anterior es verdadero tanto para una matriz simple (párrafo 1). Para un VLA (matriz de longitud variable), el comportamiento no está definido si el valor de la expresión es menor o igual a cero (párrafo 5). Este es un texto normativo en el estándar C. Un compilador no puede implementarlo de manera diferente.

gcc -std=c99 -pedantic da una advertencia para el caso no VLA.

Lundin
fuente
34
"en realidad debe dar un error": la distinción entre "advertencias" y "errores" no se reconoce en el estándar (solo menciona "diagnóstico"), y la única situación en la que la compilación debe detenerse [es decir, la diferencia del mundo real entre advertencia y error] está en encontrar una #errordirectiva.
Random832
12
Para su información, como regla general, los estándares (C o C ++) solo establecen lo que los compiladores deben permitir , pero no lo que deben rechazar . En algunos casos, declararán que el compilador debe emitir un "diagnóstico", pero eso es lo más específico posible. El resto se deja al vendedor del compilador. EDITAR: Lo que dijo Random832 también.
mcmcc
8
@Lundin "Un compilador no puede construir un binario que contenga matrices de longitud cero". El estándar no dice absolutamente nada de eso. Solo dice que debe generar al menos un mensaje de diagnóstico cuando se le da un código fuente que contiene una matriz con una expresión constante de longitud cero para su tamaño. La única circunstancia bajo la cual el estándar prohíbe a un compilador construir un binario es si encuentra una #errordirectiva de preprocesador.
Random832
55
@Lundin Generar un binario para todos los casos correctos satisface el n. ° 1, y generar o no generar uno para casos incorrectos no lo afectaría. Imprimir una advertencia es suficiente para # 3. Este comportamiento no tiene relevancia para el n. ° 2, ya que el estándar no define el comportamiento de este código fuente.
Random832
13
@Lundin: El punto es que su declaración está equivocada; compiladores conformes se les permite construir un binario que contiene un arrays de longitud cero, siempre que un diagnóstico se emite.
Keith Thompson
85

Según el estándar, no está permitido.

Sin embargo, ha sido una práctica actual en los compiladores de C tratar esas declaraciones como una declaración de miembro de matriz flexible ( FAM ) :

C99 6.7.2.1, §16 : Como caso especial, el último elemento de una estructura con más de un miembro nombrado puede tener un tipo de matriz incompleto; Esto se llama un miembro de matriz flexible.

La sintaxis estándar de una FAM es:

struct Array {
  size_t size;
  int content[];
};

La idea es que luego lo asigne así:

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

También puede usarlo estáticamente (extensión gcc):

Array a = { 3, { 1, 2, 3 } };

Esto también se conoce como estructuras con relleno de cola (este término es anterior a la publicación del estándar C99) o estructura pirateada (gracias a Joe Wreschnig por señalarlo).

Sin embargo, esta sintaxis fue estandarizada (y los efectos garantizados) solo recientemente en C99. Antes era necesario un tamaño constante.

  • 1 era el camino portátil a seguir, aunque era bastante extraño.
  • 0 fue mejor para indicar la intención, pero no es legal en lo que respecta a la Norma y algunos compiladores la respaldaron como una extensión (incluido gcc).

Sin embargo, la práctica de relleno de cola se basa en el hecho de que el almacenamiento está disponible (cuidado malloc), por lo que no es adecuado para el uso de la pila en general.

Matthieu M.
fuente
@Lundin: No he visto ningún VLA aquí, todos los tamaños se conocen en tiempo de compilación. El término de matriz flexible proviene de gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html y califico int content[];aquí hasta donde yo entiendo. Dado que no soy muy inteligente en términos de arte en C ... ¿podría confirmar si mi razonamiento parece correcto?
Matthieu M.
@MatthieuM .: C99 6.7.2.1, §16: Como caso especial, el último elemento de una estructura con más de un miembro nombrado puede tener un tipo de matriz incompleto; Esto se llama un miembro de matriz flexible.
Christoph
Este idioma también se conoce con el nombre de "struct hack" , y he conocido a más personas familiarizadas con ese nombre que "estructura con relleno de cola" (nunca lo había escuchado antes, excepto tal vez como una referencia genérica al relleno de una estructura para la futura compatibilidad ABI ) o "miembro de matriz flexible" que escuché por primera vez en C99.
1
Usar un tamaño de matriz de 1 para el pirateo de estructuras evitaría que los compiladores graznaran, pero solo era "portátil" porque los escritores de compiladores eran lo suficientemente buenos como para reconocer tal uso como un estándar de facto. Si no fuera por la prohibición de las matrices de tamaño cero, el uso consecuente del programador de matrices de un solo elemento como un sustituto malo y la actitud histórica de los escritores de compiladores de que deberían satisfacer las necesidades del programador incluso cuando el Estándar no lo requiere, los escritores de compiladores podrían fácil y útilmente han optimizado foo[x]para foo[0]cada vez que fooera una matriz de un solo elemento.
supercat
1
@RobertSsupportsMonicaCellio: como se muestra explícitamente en la respuesta, pero al final . También he cargado la explicación por adelantado, para aclararla desde el principio.
Matthieu M.
58

En Standard C y C ++, la matriz de tamaño cero no está permitida.

Si está utilizando GCC, compílelo con la -pedanticopción. Dará advertencia , diciendo:

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

En caso de C ++, da una advertencia similar.

Nawaz
fuente
9
En Visual C ++ 2010:error C2466: cannot allocate an array of constant size 0
Mark Ransom
44
-Werror simplemente convierte todas las advertencias en errores, eso no soluciona el comportamiento incorrecto del compilador GCC.
Lundin
C ++ Builder 2009 también da un error correctamente:[BCC32 Error] test.c(3): E2021 Array must have at least one element
Lundin
1
En lugar de -pedantic -Werror, también podrías hacerlo-pedantic-errors
Stephan Dollberg
3
Una matriz de tamaño cero no es lo mismo que una matriz de tamaño cero std::array. (Aparte: recuerdo pero no puedo encontrar la fuente de que los VLA fueron considerados y rechazados explícitamente de estar en C ++.)
27

Es totalmente ilegal, y siempre lo ha sido, pero muchos compiladores olvidan señalar el error. No estoy seguro de por qué quieres hacer esto. El único uso que conozco es desencadenar un error de tiempo de compilación desde un booleano:

char someCondition[ condition ];

Si conditiones falso, aparece un error de tiempo de compilación. Sin embargo, debido a que los compiladores sí permiten esto, he empezado a usar:

char someCondition[ 2 * condition - 1 ];

Esto da un tamaño de 1 o -1, y nunca he encontrado un compilador que acepte un tamaño de -1.

James Kanze
fuente
Este es un truco interesante para usarlo.
Alex Koay
10
Es un truco común en la metaprogramación, creo. No me sorprendería si las implementaciones de lo STATIC_ASSERTusaran.
James Kanze
¿Por qué no solo:#if condition \n #error whatever \n #endif
Jerfov2
1
@ Jerfov2 porque la condición puede no conocerse en el tiempo de preprocesamiento, solo el tiempo de compilación
rmeador
9

Agregaré que hay una página completa de la documentación en línea de gcc sobre este argumento.

Algunas citas:

Las matrices de longitud cero están permitidas en GNU C.

En ISO C90, tendría que dar a los contenidos una longitud de 1

y

Las versiones de GCC anteriores a la 3.0 permitían que las matrices de longitud cero se inicializaran estáticamente, como si fueran matrices flexibles. Además de aquellos casos que fueron útiles, también permitió inicializaciones en situaciones que corromperían datos posteriores

para que puedas

int arr[0] = { 1 };

y bum :-)

xanatos
fuente
¿Puedo hacer como int a[0]entonces a[0] = 1 a[1] = 2?
Suraj Jain
2
@SurajJain Si desea sobrescribir su pila :-) C no comprueba el índice frente al tamaño de la matriz que está escribiendo, por lo que puede hacerlo, a[100000] = 5pero si tiene suerte, simplemente bloqueará su aplicación, si tiene suerte: -)
xanatos
Int a [0]; significa una matriz variable (matriz de tamaño cero), ¿cómo puedo asignarla ahora?
Suraj Jain
@SurajJain ¿Qué parte de "C no comprueba el índice frente al tamaño de la matriz que está escribiendo" no está claro? No hay comprobación de índice en C, puede escribir después del final de la matriz y bloquear la computadora o sobrescribir fragmentos preciosos de su memoria. Entonces, si tiene una matriz de 0 elementos, puede escribir después del final de los 0 elementos.
xanatos
Ver esto quora.com/…
Suraj Jain
9

Otro uso de matrices de longitud cero es para hacer un objeto de longitud variable (anterior a C99). Las matrices de longitud cero son diferentes de las matrices flexibles que tienen [] sin 0.

Citado de gcc doc :

Las matrices de longitud cero están permitidas en GNU C. Son muy útiles como el último elemento de una estructura que es realmente un encabezado para un objeto de longitud variable:

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

En ISO C99, usaría un miembro de matriz flexible, que es ligeramente diferente en sintaxis y semántica:

  • Los miembros de la matriz flexible se escriben como contenido [] sin el 0.
  • Los miembros de la matriz flexible tienen un tipo incompleto, por lo que no se puede aplicar el operador sizeof.

Un ejemplo del mundo real es matrices de longitud cero de struct kdbus_itemen kdbus.h (un módulo del kernel de Linux).

Duque
fuente
2
En mi humilde opinión, no había una buena razón para que el Estándar prohibiera las matrices de longitud cero; podría tener objetos de tamaño cero bien como miembros de una estructura y considerarlos void*con fines aritméticos (por lo que estaría prohibido sumar o restar punteros a objetos de tamaño cero). Si bien los miembros de matriz flexible son en su mayoría mejores que las matrices de tamaño cero, también pueden actuar como una especie de "unión" para alias de cosas sin agregar un nivel adicional de indirección "sintáctica" a lo que sigue (por ejemplo, dado que struct foo {unsigned char as_bytes[0]; int x,y; float z;}uno puede acceder a los miembros x... z...
supercat
... directamente sin tener que decir myStruct.asFoo.x, por ejemplo , etc. Además, IIRC, C grazna en cualquier esfuerzo por incluir un miembro de matriz flexible dentro de una estructura, lo que hace imposible tener una estructura que incluya varios otros miembros de matriz flexible de longitud conocida contenido.
supercat
@supercat, una buena razón es mantener la integridad de la regla sobre el acceso fuera de los límites de la matriz. Como último miembro de una estructura, el miembro de matriz flexible C99 logra exactamente el mismo efecto que la matriz de tamaño cero GCC, pero sin necesidad de agregar casos especiales a otras reglas. En mi humilde opinión, es una mejora que sizeof x->contentses un error en ISO C en lugar de devolver 0 en gcc. Las matrices de tamaño cero que no son miembros de estructura introducen muchos otros problemas.
MM
@MM: ¿Qué problemas causarían si restar dos punteros iguales a un objeto de tamaño cero se definiera como ceder cero (como restar punteros iguales a cualquier tamaño de objeto), y restar punteros desiguales a objetos de tamaño cero se definió como ceder ¿Valor no especificado? Si el Estándar hubiera especificado que una implementación puede permitir que una estructura que contiene una FAM se incruste dentro de otra estructura, siempre que el siguiente elemento en la última estructura sea una matriz con el mismo tipo de elemento que la FAM o una estructura que comience con dicha matriz , y siempre que ...
supercat
... reconoce al FAM como alias de la matriz (si las reglas de alineación harían que las matrices aterrizaran en diferentes desplazamientos, se requeriría un diagnóstico), eso habría sido muy útil. Tal como están las cosas, no hay una buena manera de tener un método que acepte punteros a estructuras del formato general struct {int n; THING dat[];}y pueda funcionar con elementos de duración estática o automática.
supercat
6

Las declaraciones de matriz de tamaño cero dentro de las estructuras serían útiles si se permitieran, y si la semántica fuera tal que (1) forzarían la alineación pero de lo contrario no asignarían ningún espacio, y (2) indexar la matriz se consideraría un comportamiento definido en el caso en el que el puntero resultante estaría dentro del mismo bloque de memoria que la estructura. Tal comportamiento nunca fue permitido por ningún estándar C, pero algunos compiladores más antiguos lo permitieron antes de que se convirtiera en estándar para que los compiladores permitieran declaraciones de matriz incompletas con corchetes vacíos.

El truco de la estructura, como se implementa comúnmente usando una matriz de tamaño 1, es dudoso y no creo que haya ningún requisito de que los compiladores se abstengan de romperlo. Por ejemplo, yo esperaría que si un compilador ve int a[1], estaría dentro de sus derechos respecto a a[i]como a[0]. Si alguien intenta solucionar los problemas de alineación del pirateo de estructuras a través de algo como

typedef struct {
  uint32_t tamaño;
  uint8_t datos [4]; // Use cuatro, para evitar que el relleno arroje el tamaño de la estructura
}

un compilador puede ser inteligente y asumir que el tamaño de la matriz es realmente cuatro:

; Como esta escrito
  foo = myStruct-> datos [i];
; Según lo interpretado (suponiendo hardware little-endian)
  foo = ((* (uint32_t *) myStruct-> data) >> (i << 3)) & 0xFF;

Tal optimización puede ser razonable, especialmente si myStruct->datase puede cargar en un registro en la misma operación que myStruct->size. No sé nada en el estándar que prohibiría tal optimización, aunque, por supuesto, rompería cualquier código que pudiera esperar acceder a cosas más allá del cuarto elemento.

Super gato
fuente
1
El miembro de matriz flexible se agregó a C99 como una versión legítima del pirateo de la estructura
MM
El Estándar dice que los accesos a diferentes miembros de la matriz no entran en conflicto, lo que tenderá a hacer que esa optimización sea imposible.
Ben Voigt
@BenVoigt: el estándar del lenguaje C no especifica el efecto de escribir un byte y leer el contenido de una palabra simultáneamente, pero el 99.9% de los procesadores especifican que la escritura tendrá éxito y la palabra contendrá la versión nueva o la antigua del byte junto con el contenido inalterado de los otros bytes. Si un compilador apunta a dichos procesadores, ¿cuál sería el conflicto?
supercat
@supercat: el estándar del lenguaje C garantiza que las escrituras simultáneas en dos elementos de matriz diferentes no entren en conflicto. Entonces su argumento de que (leer mientras escribe) funciona bien, no es suficiente.
Ben Voigt
@BenVoigt: Si una pieza de código escribiera, por ejemplo, en los elementos de matriz 0, 1 y 2 en alguna secuencia, no se permitiría leer los cuatro elementos en un largo, modificar tres y escribir los cuatro, pero yo cree que se permitiría leer los cuatro en un largo, modificar tres, escribir los 16 bits inferiores como un corto, y los bits 16-23 como un byte. ¿No estarías de acuerdo con eso? Y el código que solo necesitaba leer elementos de la matriz se les permitiría simplemente leerlos en un largo y usarlos.
supercat