¿Por qué las matrices C no pueden tener longitud 0?

13

El estándar C11 dice que las matrices, tanto de tamaño variable como de longitud variable "tendrán un valor mayor que cero". ¿Cuál es la justificación para no permitir una longitud de 0?

Especialmente para matrices de longitud variable, tiene mucho sentido tener un tamaño de cero de vez en cuando. También es útil para matrices estáticas cuando su tamaño es de una macro o una opción de configuración de compilación.

Curiosamente, GCC (y clang) proporcionan extensiones que permiten matrices de longitud cero. Java también permite matrices de longitud cero.

Kevin Cox
fuente
77
stackoverflow.com/q/8625572 ... "Una matriz de longitud cero sería difícil y confusa para conciliar con el requisito de que cada objeto tenga una dirección única".
Robert Harvey
3
@RobertHarvey: Dado struct { int p[1],q[1]; } foo; int *pp = p+1;, ppsería un puntero legítimo, pero *ppno tendría una dirección única. ¿Por qué no podría sostenerse la misma lógica con una matriz de longitud cero? Digamos que dado int q[0]; dentro de una estructura , qse referiría a una dirección cuya validez sería como la del p+1ejemplo anterior.
supercat
@DocBrown Desde el estándar C11 6.7.6.2.5 hablando de la expresión utilizada para determinar el tamaño de un VLA "... cada vez que se evalúa tendrá un valor mayor que cero". No sé sobre C99 (y parece extraño que lo cambien), pero parece que no puede tener una longitud de cero.
Kevin Cox
@KevinCox: ¿hay disponible una versión en línea gratuita del estándar C11 (o la parte en cuestión)?
Doc Brown
La versión final no está disponible de forma gratuita (qué pena), pero puede descargar borradores. El último borrador disponible es open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf .
Kevin Cox

Respuestas:

11

El problema que apostaría es que las matrices en C son solo punteros al comienzo de un fragmento de memoria asignado. Tener un tamaño 0 significaría que tienes un puntero para ... ¿nada? No se puede tener nada, por lo que habría tenido que haber algo arbitrario elegido. No puede usar null, porque entonces sus matrices de longitud 0 se verían como punteros nulos. Y en ese punto, cada implementación diferente va a elegir diferentes comportamientos arbitrarios, lo que conducirá al caos.

Telastyn
fuente
77
@delnan: Bueno, si quieres ser pedante al respecto, la aritmética de matriz y puntero se define de modo que un puntero se pueda usar convenientemente para acceder a una matriz o simular una matriz. En otras palabras, la aritmética del puntero y la indexación de la matriz son equivalentes en C. Pero el resultado es el mismo de todos modos ... si la longitud de la matriz es cero, todavía no estás apuntando a nada.
Robert Harvey
3
@RobertHarvey Todo cierto, pero sus palabras de cierre (y la respuesta completa en retrospectiva) parecen una forma confusa y confusa de explicar que esa matriz (creo que eso es lo que esta respuesta llama "un trozo de memoria asignado") sizeof0, y cómo eso causaría problemas. Todo eso puede explicarse utilizando los conceptos y la terminología adecuados sin pérdida de brevedad o claridad. Mezclar matrices y punteros solo corre el riesgo de difundir la idea errónea de matrices = punteros (que es más importante en otros contextos) sin ningún beneficio.
2
" No puede usar nulo, porque entonces sus matrices de longitud 0 se verían como punteros nulos ", en realidad eso es exactamente lo que hace Delphi. Las dynarrays vacías y las cuerdas largas vacías son punteros técnicamente nulos.
JensG
3
-1, estoy lleno de @delnan aquí. Esto no explica nada, especialmente en el contexto de lo que escribió el OP sobre algunos compiladores importantes que admiten el concepto de matrices de longitud cero. Estoy bastante seguro de que se pueden proporcionar matrices de longitud cero en C de una manera independiente de la implementación, no "conduciendo al caos".
Doc Brown
6

Veamos cómo se despliega una matriz en la memoria:

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

Tenga en cuenta que no hay un objeto separado llamado arrque almacene la dirección del primer elemento; cuando aparece una matriz en una expresión, C calcula la dirección del primer elemento según sea necesario.

Entonces, pensemos en esto: una matriz de 0 elementos no tendría almacenamiento aparte, lo que significa que no hay nada para calcular la dirección de la matriz (dicho de otra manera, no hay mapeo de objetos para el identificador). Es como decir: "Quiero crear una intvariable que no ocupe memoria". Es una operación sin sentido.

Editar

Las matrices Java son animales completamente diferentes de las matrices C y C ++; no son un tipo primitivo, sino un tipo de referencia derivado de Object.

Editar 2

Un punto que aparece en los comentarios a continuación: la restricción "mayor que 0" solo se aplica a las matrices donde el tamaño se especifica a través de una expresión constante ; Se permite que un VLA tenga una longitud 0 La declaración de un VLA con una expresión no constante con valor 0 no es una violación de restricción, pero invoca un comportamiento indefinido.

Está claro que los VLA son animales diferentes de las matrices regulares , y su implementación puede permitir un tamaño 0 . No se pueden declarar staticni al alcance del archivo, porque el tamaño de dichos objetos debe conocerse antes de que se inicie el programa.

Tampoco vale nada que a partir de C11, las implementaciones no sean necesarias para admitir VLA.

John Bode
fuente
3
Lo siento, pero en mi humilde opinión te estás perdiendo el punto, al igual que Telastyn. Las matrices de longitud cero pueden tener mucho sentido, y las implementaciones existentes como las que nos contó el OP muestran que se puede hacer.
Doc Brown
@DocBrown: Primero, estaba abordando por qué el estándar de idioma probablemente no los permita. En segundo lugar, me gustaría un ejemplo de dónde tiene sentido una matriz de longitud 0, porque honestamente no puedo pensar en una. La implementación más probable es tratar T a[0]como T *a, pero ¿por qué no simplemente usar T *a?
John Bode
Lo siento, pero no compro el "razonamiento teórico" de por qué el estándar prohíbe esto. Lea mi respuesta sobre cómo la dirección podría calcularse realmente fácilmente. Y le sugiero que siga el enlace en el primer comentario de Robert Harveys debajo de la pregunta y lea la segunda respuesta, hay un ejemplo útil.
Doc Brown
@DocBrown: Ah. El structhack. Nunca lo he usado personalmente; nunca trabajó en un problema que necesitaba un structtipo de tamaño variable .
John Bode
2
Y para no olvidar AFAIK desde C99, C permite arreglos de longitud variable. Y cuando el tamaño de la matriz es un parámetro, no tener que tratar un valor de 0 como un caso especial puede simplificar muchos programas.
Doc Brown
2

Por lo general, querrá que su matriz de tamaño cero (de hecho variable) sepa su tamaño en tiempo de ejecución. Luego empaquete eso en structy use miembros de matriz flexibles , como por ejemplo:

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

Obviamente, el miembro de la matriz flexible tiene que ser el último structy debe tener algo antes. A menudo eso sería algo relacionado con la longitud real ocupada en tiempo de ejecución de ese miembro de matriz flexible.

Por supuesto que asignarías:

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

AFAIK, esto ya era posible en C99, y es muy útil.

Por cierto, los miembros de matriz flexible no existen en C ++ (porque sería difícil definir cuándo y cómo deberían construirse y destruirse). Ver sin embargo el futuro std :: dynarray

Basile Starynkevitch
fuente
Ya sabes, podrían limitarse a tipos triviales, y no habría dificultad.
Deduplicador
2

Si la expresión type name[count]está escrita en alguna función, entonces le indica al compilador de C que asigne en los sizeof(type)*countbytes del marco de la pila y calcule la dirección del primer elemento de la matriz.

Si la expresión type name[count]se escribe fuera de todas las definiciones de funciones y estructuras, entonces le indica al compilador de C que asigne en los sizeof(type)*countbytes del segmento de datos y calcule la dirección del primer elemento en la matriz.

nameen realidad es un objeto constante que almacena la dirección del primer elemento en la matriz y cada objeto que almacena una dirección de alguna memoria se llama puntero, por lo que esta es la razón por la que se trata namecomo un puntero en lugar de una matriz. Tenga en cuenta que solo se puede acceder a las matrices en C a través de punteros.

Si countes una expresión constante que se evalúa a cero, entonces le dice al compilador de C que asigne cero bytes en el marco de la pila o en el segmento de datos y devuelva la dirección del primer elemento de la matriz, pero el problema al hacerlo es que el primer elemento de la matriz de longitud cero no existe y no puede calcular la dirección de algo que no existe.

Esto es racional ese elemento no. count+1no existe en la countmatriz de longitud, por lo que esta es la razón por la que el compilador de C prohíbe definir la matriz de longitud cero como variable dentro y fuera de una función, porque ¿cuál es el contenido de nameentonces? Que direccionname almacena exactamente?

Si pes un puntero, entonces la expresión p[n]es equivalente a*(p + n)

Donde el asterisco * en la expresión derecha es la operación de desreferencia del puntero, lo que significa acceder a la memoria apuntada p + no acceder a la memoria cuya dirección está almacenada p + n, donde p + nes la expresión del puntero, toma la dirección py agrega a esta dirección el número nmultiplica el tamaño del tipo de puntero p.

¿Es posible agregar una dirección y un número?

Sí, es posible, porque la dirección es un entero sin signo comúnmente representado en notación hexadecimal.

usuario307542
fuente
Muchos compiladores solían permitir declaraciones de matriz de tamaño cero antes de que el Estándar lo prohibiera, y muchos continúan permitiendo tales declaraciones como una extensión. Dichas declaraciones no causarán ningún problema si uno reconoce que un objeto de tamaño Ntiene N+1direcciones asociadas, la primera Nde las cuales identifica bytes únicos y la última Nde las cuales cada punto acaba de pasar uno de esos bytes. Tal definición funcionaría bien incluso en el caso degenerado donde Nes 0.
supercat
1

Si desea un puntero a una dirección de memoria, declare uno. Una matriz en realidad apunta a un trozo de memoria que ha reservado. Las matrices decaen a punteros cuando se pasan a funciones, pero si la memoria a la que apuntan está en el montón, no hay problema. No hay razón para declarar una matriz de tamaño cero.

ncmathsadist
fuente
2
En general, no haría esto directamente, sino como resultado de una macro o al declarar una matriz de longitud variable con datos dinámicos.
Kevin Cox
Una matriz no apunta, nunca. Puede contener punteros, y en la mayoría de los contextos en realidad se usa un puntero al primer elemento, pero esa es una historia diferente.
Deduplicador
1
El nombre de la matriz ES un puntero constante a la memoria contenida en la matriz.
ncmathsadist
1
No, el nombre de la matriz decae a un puntero al primer elemento, en la mayoría de los contextos. La diferencia es a menudo crucial.
Deduplicador
1

Desde los días del C89 original, cuando un Estándar C especificaba que algo tenía un Comportamiento Indefinido, lo que eso significaba era "Hacer lo que sea que haga que una implementación en una plataforma objetivo particular sea la más adecuada para su propósito". Los autores de la Norma no querían tratar de adivinar qué comportamientos podrían ser más adecuados para un propósito particular. Las implementaciones existentes de C89 con extensiones VLA podrían haber tenido comportamientos diferentes, pero lógicos, cuando se les dio un tamaño de cero (por ejemplo, algunos podrían haber tratado la matriz como una expresión de dirección que genera NULL, mientras que otros la trataron como una expresión de dirección que podría ser igual a la dirección de otra variable arbitraria, pero con seguridad podría agregar cero sin atrapar). Si algún código pudiera haberse basado en un comportamiento tan diferente, los autores de la Norma no

En lugar de tratar de adivinar lo que podrían hacer las implementaciones, o sugerir que cualquier comportamiento debería considerarse superior a cualquier otro, los autores de la Norma simplemente permitieron que los implementadores usaran el juicio al manejar ese caso lo mejor que creían conveniente. Las implementaciones que usan malloc () detrás de escena pueden tratar la dirección de la matriz como NULL (si malloc de tamaño cero produce un valor nulo), aquellas que usan cálculos de direcciones de pila podrían generar un puntero que coincida con la dirección de alguna otra variable, y algunas otras implementaciones podrían funcionar otras cosas. No creo que esperaran que los escritores de compiladores hicieran todo lo posible para que el caso de la esquina de tamaño cero se comportara de manera deliberadamente inútil.

Super gato
fuente