La norme C11 stipule que les tableaux, à la fois dimensionnés et de longueur variable "doivent avoir une valeur supérieure à zéro". Quelle est la justification pour ne pas autoriser une longueur de 0?
Surtout pour les tableaux de longueur variable, il est parfaitement logique d'avoir une taille de zéro de temps en temps. Il est également utile pour les tableaux statiques lorsque leur taille provient d'une option de configuration de macro ou de build.
Fait intéressant, GCC (et clang) fournissent des extensions qui permettent des tableaux de longueur nulle. Java autorise également les tableaux de longueur zéro.
struct { int p[1],q[1]; } foo; int *pp = p+1;
,pp
serait un pointeur légitime, mais*pp
n'aurait pas d'adresse unique. Pourquoi la même logique ne pourrait-elle pas tenir avec un tableau de longueur nulle? Supposons que, donnéint q[0];
dans une structure ,q
se réfère à une adresse dont la validité serait similaire à celle de l'p+1
exemple ci-dessus.Réponses:
Le problème que je parierais est que les tableaux C ne sont que des pointeurs vers le début d'un morceau de mémoire alloué. Avoir une taille 0 signifierait que vous avez un pointeur vers ... rien? Vous ne pouvez rien avoir, alors il aurait fallu choisir quelque chose d’arbitraire. Vous ne pouvez pas utiliser
null
, car vos tableaux de longueur 0 ressembleraient à des pointeurs nuls. Et à ce stade, chaque implémentation différente va choisir des comportements arbitraires différents, conduisant au chaos.la source
sizeof
0, et comment cela causerait des problèmes. Tout cela peut être expliqué en utilisant les concepts et la terminologie appropriés sans perte de brièveté ou de clarté. Mélanger les tableaux et les pointeurs ne risque que de répandre la conception erronée des tableaux = pointeurs (ce qui est plus important dans d'autres contextes) sans aucun avantage.Voyons comment un tableau est généralement disposé en mémoire:
Notez qu'il n'y a pas d'objet distinct nommé
arr
qui stocke l'adresse du premier élément; lorsqu'un tableau apparaît dans une expression, C calcule l'adresse du premier élément selon les besoins.Alors, réfléchissons à ceci: un tableau à 0 élément n'aurait pas de stockage mis de côté, ce qui signifie qu'il n'y a rien pour calculer l'adresse du tableau (en d'autres termes, il n'y a pas de mappage d'objet pour l'identifiant). C'est comme dire: «Je veux créer une
int
variable qui ne prend pas de mémoire». C'est une opération absurde.Éditer
Les tableaux Java sont des animaux complètement différents des tableaux C et C ++; ce n'est pas un type primitif, mais un type de référence dérivé de
Object
.Modifier 2
Un point soulevé dans les commentaires ci-dessous - la contrainte "supérieur à 0" ne s'applique qu'aux tableaux dont la taille est spécifiée via une expression constante ;
un VLA est autorisé à avoir une longueur 0 Ladéclaration d'un VLA avec une expression non constante de valeur 0 n'est pas une violation de contrainte, mais elle appelle un comportement non défini.Il est clair que les VLA sont des animaux différents des tableaux réguliers
, et leur implémentation peut permettre une taille 0. Ils ne peuvent pas être déclarésstatic
ou à portée de fichier, car la taille de ces objets doit être connue avant le démarrage du programme.Cela ne vaut également rien qu'à compter de C11, les implémentations ne sont pas nécessaires pour prendre en charge les VLA.
la source
T a[0]
commeT *a
, mais alors pourquoi ne pas simplement l'utiliserT *a
?struct
hack. Je ne l'ai jamais utilisé personnellement; n'a jamais travaillé sur un problème nécessitant unstruct
type de taille variable .Vous voudriez généralement que votre tableau de taille zéro (en fait variable) connaisse sa taille au moment de l'exécution. Emballez ensuite cela dans un
struct
et utilisez des membres de tableau flexibles , comme par exemple:De toute évidence, le membre du tableau flexible doit être le dernier dans son
struct
et vous devez avoir quelque chose avant. Souvent, cela serait lié à la longueur réelle occupée par l'exécution de ce membre de groupe flexible.Bien sûr, vous alloueriez:
AFAIK, c'était déjà possible en C99, et c'est très utile.
BTW, les membres de tableau flexibles n'existent pas en C ++ (car il serait difficile de définir quand et comment ils doivent être construits et détruits). Voir cependant le futur std :: dynarray
la source
Si l'expression
type name[count]
est écrite dans une fonction, vous dites au compilateur C d'allouer sur lessizeof(type)*count
octets de trame de pile et de calculer l'adresse du premier élément du tableau.Si l'expression
type name[count]
est écrite en dehors de toutes les définitions de fonctions et de structures, vous dites au compilateur C d'allouer sur lessizeof(type)*count
octets de segment de données et de calculer l'adresse du premier élément du tableau.name
est en fait un objet constant qui stocke l'adresse du premier élément du tableau et chaque objet qui stocke une adresse de mémoire est appelé pointeur, c'est donc la raison pour laquelle vous traitezname
comme un pointeur plutôt qu'un tableau. Notez que les tableaux en C ne sont accessibles que via des pointeurs.Si
count
est une expression constante qui évalue à zéro, vous dites au compilateur C d'allouer zéro octet sur la trame de pile ou le segment de données et de renvoyer l'adresse du premier élément du tableau, mais le problème en faisant cela est que le premier élément de tableau de longueur nulle n'existe pas et vous ne pouvez pas calculer l'adresse de quelque chose qui n'existe pas.C'est rationnel que l'élément non.
count+1
n'existe pas dans uncount
tableau de longueur, c'est pourquoi le compilateur C interdit de définir un tableau de longueur nulle comme variable dans et en dehors d'une fonction, car quel est le contenu dename
alors? Quelle adressename
stocke exactement?Si
p
est un pointeur, l'expressionp[n]
est équivalente à*(p + n)
Lorsque l'astérisque * dans l'expression de droite est une opération de déréférencement du pointeur, ce qui signifie accéder à la mémoire pointée par
p + n
ou accéder à la mémoire dont l'adresse est stockéep + n
, oùp + n
est l'expression du pointeur, il prend l'adresse dep
et ajoute à cette adresse le nombren
multiplier le taille du type du pointeurp
.Est-il possible d'ajouter une adresse et un numéro?
Oui, c'est possible, car l'adresse est un entier non signé généralement représenté en notation hexadécimale.
la source
N
aN+1
des adresses associées, dont la premièreN
identifie des octets uniques et la dernièreN
dont chaque point vient de dépasser un de ces octets. Une telle définition fonctionnerait très bien même dans le cas dégénéré oùN
est 0.Si vous voulez un pointeur vers une adresse mémoire, déclarez-en un. Un tableau pointe en fait sur un morceau de mémoire que vous avez réservé. Les tableaux se désintègrent aux pointeurs lorsqu'ils sont passés aux fonctions, mais si la mémoire vers laquelle ils pointent est sur le tas, pas de problème. Il n'y a aucune raison de déclarer un tableau de taille zéro.
la source
Depuis l'époque de la C89 d'origine, lorsqu'une norme C spécifiait que quelque chose avait un comportement indéfini, cela signifiait «Faire tout ce qui rendrait une implémentation sur une plate-forme cible particulière plus adaptée à son objectif». Les auteurs de la norme ne voulaient pas essayer de deviner quels comportements pourraient être les plus adaptés à un usage particulier. Les implémentations C89 existantes avec des extensions VLA peuvent avoir eu des comportements différents, mais logiques, quand on leur a donné une taille de zéro (par exemple, certains pourraient avoir traité le tableau comme une expression d'adresse donnant NULL, tandis que d'autres le traitant comme une expression d'adresse qui pourrait être égale à l'adresse de une autre variable arbitraire, mais pourrait sans risque y avoir ajouté zéro sans piéger). Si un code pouvait se fonder sur un comportement aussi différent, les auteurs de la norme ne
Plutôt que d'essayer de deviner ce que les implémentations pourraient faire ou de suggérer que tout comportement devrait être considéré comme supérieur à tout autre, les auteurs de la norme ont simplement permis aux implémenteurs de faire preuve de jugement dans le traitement de ce cas comme ils l'entendaient. Les implémentations qui utilisent malloc () en arrière-plan peuvent traiter l'adresse du tableau comme NULL (si malloc à taille zéro renvoie null), celles qui utilisent des calculs d'adresse de pile peuvent produire un pointeur qui correspond à l'adresse d'une autre variable, et d'autres implémentations peuvent faire autres choses. Je ne pense pas qu'ils s'attendaient à ce que les auteurs de compilateurs fassent tout leur possible pour que le boîtier de coin de taille zéro se comporte de manière délibérément inutile.
la source