web-dev-qa-db-fra.com

Pourquoi est-ce que char [] [] = {{...}, {...}} n'est pas possible si on lui donne explicitement un tableau multidimensionnel?

J'ai parcouru cet article. Je comprends les règles expliquées, mais je me demande ce qui empêche exactement le compilateur d'accepter la syntaxe suivante lors de la définition d'un tableau multidimensionnel constant et de l'initialisation directe avec des valeurs connues de type donné:

const int multi_arr1[][] = {{1,2,3}, {1,2,3}}; // why not?
const int multi_arr2[][3] = {{1,2,3}, {1,2,3}}; // OK

error: declaration of 'multi_arr1' as multidimensional array must have bounds
       for all dimensions except the first

Ce qui empêche le compilateur de regarder vers la droite et de se rendre compte que nous avons affaire à 3 éléments pour chaque "sous-tableau" ou éventuellement de renvoyer une erreur uniquement pour les cas où le programmeur passe par ex. un nombre différent d'éléments pour chaque sous-tableau comme {1,2,3}, {1,2,3,4}?

Par exemple, lorsqu'il s'agit d'un tableau de caractères 1D, le compilateur peut regarder la chaîne sur le côté droit de = et c'est valable:

const char str[] = "Str";

Je voudrais comprendre ce qui se passe pour que le compilateur ne soit pas en mesure de déduire les dimensions du tableau et de calculer la taille pour l'allocation car il me semble maintenant que le compilateur a toutes les informations nécessaires pour le faire. Qu'est-ce que j'oublie ici?

24
esgaldir

Obliger le compilateur à déduire les dimensions internes des initialiseurs exigerait que le compilateur fonctionne rétroactivement d'une manière que la norme évite.

La norme permet aux objets en cours d'initialisation de se référer à eux-mêmes. Par exemple:

struct foo { struct foo *next; int value; } head = { &head, 0 };

Cela définit un nœud d'une liste liée qui pointe vers lui-même initialement. (Vraisemblablement, plus de nœuds seront insérés plus tard.) Ceci est valide car C 2011 [N1570] 6.2.1 7 dit que l'identifiant head "a une portée qui commence juste après la fin de son déclarateur." Un déclarateur est la partie de la grammaire d'une déclaration qui inclut le nom de l'identifiant ainsi que les parties tableau, fonction et/ou pointeur de la déclaration (par exemple, f(int, float) et *a[3] Sont des déclarants, dans des déclarations telles que float f(int, float) ou int *a[3]).

En raison de 6.2.1 7, un programmeur pourrait écrire cette définition:

void *p[][1] = { { p[1] }, { p[0] } };

Considérez l'initialiseur p[1]. Il s'agit d'un tableau, il est donc automatiquement converti en pointeur vers son premier élément, p[1][0]. Le compilateur connaît cette adresse car il sait que p[i] Est un tableau de 1 void * (Pour toute valeur de i). Si le compilateur ne savait pas la taille de p[i], Il ne pouvait pas calculer cette adresse. Donc, si la norme C nous permettait d'écrire:

void *p[][] = { { p[1] }, { p[0] } };

alors le compilateur devrait continuer à analyser au-delà de p[1] afin qu'il puisse compter le nombre d'initialiseurs donnés pour la deuxième dimension (un seul dans ce cas, mais nous devons numériser au moins vers } pour voir cela, et il pourrait y en avoir beaucoup plus), puis revenez en arrière et calculez la valeur de p[1].

La norme évite de forcer les compilateurs à effectuer ce type de travail en plusieurs passes. Obliger les compilateurs à déduire les dimensions intérieures violerait cet objectif, donc la norme ne le fait pas.

(En fait, je pense que la norme n'exige peut-être pas que le compilateur fasse plus qu'une quantité limitée d'anticipation, peut-être juste quelques caractères pendant la tokenisation et un seul jeton lors de l'analyse de la grammaire, mais je ne suis pas sûr. Certaines choses ont des valeurs inconnues jusqu'à l'heure du lien, telles que void (*p)(void) = &SomeFunction;, mais celles-ci sont remplies par l'éditeur de liens.)

27
Eric Postpischil

Il n'y a rien impossible dans l'implémentation de compilateurs qui déduirait les dimensions les plus internes des tableaux multidimensionnels en présence d'un initialiseur, cependant, c'est une fonctionnalité qui n'est PAS prise en charge par les normes C ou C++, et, évidemment, il n'a pas été une grande demande pour cette fonctionnalité de déranger.

En d'autres termes, ce que vous recherchez n'est pas pris en charge par le langage standard. Il pourrait être pris en charge si suffisamment de personnes en avaient besoin. Ils ne le font pas.

7
Armen Tsirunyan

Pour développer brièvement le commentaire:

Ce qui "bloque" le compilateur, c'est l'adhésion à la norme (pour C ou C++, ce sont des normes différentes, choisissez-en une).

Ce qui "empêche" la norme de permettre cela, c'est que personne n'a rédigé une proposition de normes pour sa mise en œuvre, qui a ensuite été acceptée.

Donc, tout ce que vous demandez, c'est pourquoi personne n'était motivé à faire quelque chose que vous jugeriez utile, et je ne peux que voir cela comme fondé sur l'opinion.

Il peut y avoir également des difficultés pratiques pour l'implémenter ou pour garder une sémantique cohérente ce n'est pas précisément la question que vous avez posée, mais elle pourrait au moins être objectivement répondable. Je soupçonne que quelqu'un pourrait surmonter ces difficultés s'il est suffisamment motivé. Vraisemblablement, personne ne l'était.

Par exemple, ( référence ), la syntaxe a[] signifie vraiment tableau de bornes inconnues . Étant donné que la limite peut être déduite dans le cas spécial lorsqu'elle est déclarée à l'aide de l'initialisation agrégée, vous la traitez comme quelque chose comme a[auto]. Peut-être que serait une meilleure proposition, car il n'a pas le bagage historique. N'hésitez pas à l'écrire vous-même si vous pensez que les avantages justifient l'effort.

4
Useless

La règle est que le compilateur détermine uniquement la première dimension du tableau par la liste d'initialisation donnée. Il s'attend à ce que la deuxième dimension soit spécifiée explicitement. Période.

3
haccks

Avec un tableau, le compilateur doit connaître la taille de chaque élément afin de pouvoir faire le calcul d'index. Par exemple

int a[3];

est un tableau entier. Le compilateur connaît la taille d'un int (généralement 4 octets) afin de pouvoir calculer l'adresse de a[x]x est un index compris entre 0 et 2.

Un tableau bidimensionnel peut être considéré comme un tableau unidimensionnel de tableaux. par exemple.

int b[2][3];

est un tableau bidimensionnel de int mais c'est également un tableau unidimensionnel de tableaux de int. c'est à dire. b[x] fait référence à un tableau de trois ints.

Même avec des tableaux de tableaux, la règle selon laquelle le compilateur doit connaître la taille de chaque élément s'applique toujours, ce qui signifie que dans un tableau de tableaux, le deuxième tableau doit être de taille fixe. Sinon, le compilateur n'a pas pu calculer l'adresse lors de l'indexation, c'est-à-dire b[x] serait impossible à calculer. D'où la raison pour laquelle multi_arr2 dans votre exemple, c'est OK, mais multi_arr1 n'est pas.

Ce qui empêche le compilateur de regarder vers la droite et de prétendre que nous traitons 3 éléments pour chaque "sous-tableau" ou peut-être renvoyer une erreur uniquement pour les cas où le programmeur passe par ex. nombre d'éléments différent pour chaque sous-réseau comme {1,2,3}, {1,2,3,4}

Probablement une limitation de l'analyseur. Au moment où il arrive à l'initialiseur, l'analyseur a déjà dépassé la déclaration. Les premiers compilateurs C étaient assez limités et le comportement ci-dessus a été défini comme prévu bien avant l'arrivée des compilateurs modernes.

1
JeremyP