Pourquoi les tableaux C ne suivent-ils pas leur longueur?

77

Quel était le raisonnement derrière ne pas stocker explicitement la longueur d'un tableau avec un tableau C?

À mon avis, les raisons sont très nombreuses mais peu favorables à la norme (C89). Par exemple:

  1. Avoir la longueur disponible dans un tampon peut empêcher un dépassement de tampon.
  2. Un style Java arr.lengthest à la fois clair et évite au programmeur de devoir garder plusieurs ints sur la pile s’il utilise plusieurs tableaux
  3. Les paramètres de fonction deviennent plus convaincants.

Mais peut-être la raison la plus motivante, à mon avis, est-elle que généralement, aucun espace n'est économisé sans conserver la longueur. J'oserais dire que la plupart des utilisations de tableaux impliquent une allocation dynamique. Certes, il peut arriver que des personnes utilisent un tableau alloué sur la pile, mais il ne s’agit que d’un seul appel de fonction *: la pile peut gérer 4 ou 8 octets supplémentaires.

Puisque le gestionnaire de tas doit suivre la taille de bloc libre utilisée par le tableau alloué dynamiquement, pourquoi ne pas rendre cette information utilisable (et ajouter la règle supplémentaire, vérifiée au moment de la compilation, qui interdit de manipuler explicitement la longueur à moins de le faire aimer se tirer une balle dans le pied).

La seule chose que je peux penser de l'autre côté est qu'aucun suivi de longueur peut avoir fait des compilateurs plus simple, mais pas que beaucoup plus simple.

* Techniquement, on pourrait écrire une sorte de fonction récursive avec un tableau avec stockage automatique, et dans ce cas (très complexe) le stockage de la longueur peut effectivement entraîner une utilisation réellement plus importante de l'espace.

VF1
la source
6
Je suppose que l’on pourrait faire valoir que, lorsque C a inclus l’utilisation de structs en tant que types de paramètre et de valeur de retour, il aurait dû inclure le sucre syntaxique pour les "vecteurs" (ou un nom quelconque), qui serait en dessous de struct avec length et d'un tableau ou d'un pointeur à l'autre . La prise en charge de niveau de langage pour cette construction commune (également lorsqu’elle est passée en tant qu’arguments séparés et non en tant que structure unique) aurait permis d’économiser un nombre incalculable de bogues et une bibliothèque standard simplifiée.
Hyde
3
Vous pouvez également trouver pourquoi Pascal n'est pas mon langage de programmation préféré, section 2.1, pour être perspicace.
34
Bien que toutes les autres réponses comportent des points intéressants, je pense que C a été écrit pour que les programmeurs en langage assembleur puissent écrire du code plus facilement et le rendre portable. Garder cela à l’esprit, le fait d’avoir automatiquement stocké une longueur de tableau AVEC un tableau aurait été une nuisance et non un inconvénient (comme cela aurait été le cas pour d’autres belles applications de revêtement de bonbons). Ces fonctionnalités semblent intéressantes de nos jours, mais à l'époque, il était souvent très difficile d'insérer un octet supplémentaire de programme ou de données dans votre système. Le gaspillage de mémoire aurait fortement limité l'adoption de C.
Dunk
6
La partie réelle de votre réponse a déjà été répondue à maintes reprises comme je l'aurais fait, mais je peux en extraire un point différent: "Pourquoi la taille d'une malloc()zone ed ne peut-elle pas être demandée de manière portable?" C'est une chose qui me fait me demander plusieurs fois.
Glglgl
5
Voter pour rouvrir. Il y a une raison quelque part, même si c'est simplement "K & R n'y a pas pensé".
Telastyn

Réponses:

106

Les tableaux C gardent une trace de leur longueur, car la longueur du tableau est une propriété statique:

int xs[42];  /* a 42-element array */

En règle générale, vous ne pouvez pas interroger cette longueur, mais vous n'en avez pas besoin, car elle est statique. Déclarez simplement une macro XS_LENGTHpour la longueur et vous avez terminé.

Le problème le plus important est que les tableaux C se dégradent implicitement en pointeurs, par exemple lorsqu'ils sont transmis à une fonction. Cela a du sens, et permet quelques belles astuces de bas niveau, mais il perd les informations sur la longueur du tableau. Une meilleure question serait donc de savoir pourquoi C a été conçu avec cette dégradation implicite des pointeurs.

Un autre problème est que les pointeurs n'ont besoin d'aucun stockage, à l'exception de l'adresse de la mémoire elle-même. C nous permet de convertir des entiers en pointeurs, des pointeurs vers d’autres pointeurs, et de traiter les pointeurs comme s’ils étaient des tableaux. Ce faisant, C n’est pas assez fou pour fabriquer une longueur de tableau, mais semble croire en la devise de Spiderman: avec un grand pouvoir, le programmeur assumera la grande responsabilité de garder une trace des longueurs et des débordements.

Amon
la source
13
Je pense que vous voulez dire, si je ne me trompe pas, que les compilateurs C gardent une trace des longueurs de tableaux statiques. Mais cela ne sert à rien pour les fonctions qui ne font qu'obtenir un pointeur.
VF1
25
@ VF1 oui. Mais l' importante chose est que les tableaux et les pointeurs sont des choses différentes dans C . En supposant que vous n'utilisez aucune extension de compilateur, vous ne pouvez généralement pas transmettre un tableau à une fonction, mais vous pouvez également passer un pointeur et indexer un pointeur comme s'il s'agissait d'un tableau. Vous vous plaignez effectivement que les pointeurs n'ont pas de longueur. Vous devriez vous plaindre que les tableaux ne peuvent pas être passés en tant qu'arguments de fonction ou qu'ils se dégradent implicitement en pointeurs.
amon
37
"Vous ne pouvez généralement pas interroger cette longueur" - en fait, vous pouvez le faire, c'est l'opérateur sizeof - sizeof (xs) renverrait 168 en supposant que les entiers ont quatre octets de long. Pour obtenir les 42, faites: sizeof (xs) / sizeof (int)
tcrosley
15
@tcrosley Cela ne fonctionne que dans le cadre de la déclaration de tableau - essayez de passer x comme paramètre à une autre fonction, puis voyez ce que sizeof (xs) vous donne ...
Gwyn Evans
26
@GwynEvans encore: les pointeurs ne sont pas des tableaux. Donc, si vous “passez un tableau en tant que paramètre à une autre fonction”, vous ne passez pas un tableau mais un pointeur. Affirmer que sizeof(xs)xsest un tableau serait quelque chose de différent dans un autre domaine est manifestement faux, car la conception de C ne permet pas aux tableaux de quitter leur champ. Si sizeof(xs)xsest un tableau est différent de sizeof(xs)xsest un pointeur, ce n'est pas surprenant, car vous comparez des pommes avec des oranges .
amon
38

Cela tenait en grande partie aux ordinateurs disponibles à l’époque. Non seulement le programme compilé devait-il s'exécuter sur un ordinateur disposant de peu de ressources, mais, chose plus importante peut-être, le compilateur lui-même devait s'exécuter sur ces machines. Au moment où Thompson a développé le C, il utilisait un PDP-7, avec 8 ko de RAM. Les fonctionnalités de langage complexes qui n'avaient pas d'analogue immédiat sur le code machine réel n'étaient tout simplement pas incluses dans le langage.

Une lecture attentive de l’ histoire du C permet de mieux comprendre ce qui précède, mais cela n’a pas été entièrement imputable aux limitations de la machine qu’ils avaient:

De plus, le langage (C) montre un pouvoir considérable pour décrire des concepts importants, par exemple des vecteurs dont la longueur varie au moment de l'exécution, avec seulement quelques règles et conventions de base. ... Il est intéressant de comparer l'approche de C avec celle de deux langues presque contemporaines, Algol 68 et Pascal [Jensen 74]. Les tableaux dans Algol 68 ont soit des limites fixes, soit sont "flexibles:" Un mécanisme considérable est nécessaire, à la fois dans la définition du langage et dans les compilateurs, pour prendre en charge les tableaux flexibles (tous les compilateurs ne les implémentent pas complètement.) tableaux et des chaînes, et cela s'est avéré confiner [Kernighan 81].

Les tableaux sont intrinsèquement plus puissants. Leur ajouter des limites limite les possibilités d'utilisation par le programmeur. De telles restrictions peuvent être utiles aux programmeurs, mais sont nécessairement aussi limitantes.

Adam Davis
la source
4
Cela répond assez bien à la question initiale. Cela et le fait que C était délibérément maintenu "discret" lorsqu'il s'agissait de vérifier ce que faisait le programmeur, afin de le rendre attrayant pour l'écriture de systèmes d'exploitation.
ClickRick
5
Excellent lien, ils ont aussi explicitement changé l'enregistrement de la longueur des chaînes pour utiliser un délimiteur to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator- et tant mieux pour ça :-)
Voo
5
Les baies non terminées s’inscrivent également dans l’approche «bare metal» de C. N'oubliez pas que le livre K & R C compte moins de 300 pages avec un didacticiel de langue, une référence et une liste des appels standard. Mon livre O'Reilly Regex est presque deux fois plus long que K & R C.
Michael Shopsin
22

À l'époque de la création de C, il restait 4 octets d'espace supplémentaire pour chaque chaîne, peu importe la longueur de sa longueur, ce qui aurait été une perte de temps!

Il y a un autre problème - rappelez-vous que C n'est pas orienté objet, donc si vous préférez le préfixe de longueur pour toutes les chaînes, il devrait être défini comme un type intrinsèque du compilateur, pas un char*. S'il s'agissait d'un type spécial, vous ne pourriez pas comparer une chaîne à une chaîne constante, c'est-à-dire:

String x = "hello";
if (strcmp(x, "hello") == 0) 
  exit;

aurait besoin d'avoir des détails spéciaux du compilateur pour convertir cette chaîne statique en chaîne, ou avoir différentes fonctions de chaîne pour prendre en compte le préfixe de longueur.

Je pense que finalement, ils n’ont tout simplement pas choisi le préfixe de longueur contrairement à Pascal.

gbjbaanb
la source
10
La vérification des limites prend aussi du temps. Trivial dans les termes d'aujourd'hui, mais quelque chose que les gens ont pris en compte quand ils se soucient d'environ 4 octets.
Gort the Robot Le
18
@StevenBurnap: ce n'est pas si trivial, même aujourd'hui, si vous êtes dans une boucle interne qui parcourt chaque pixel d'une image de 200 Mo. En général, si vous écrivez C, vous voulez aller vite et vous ne voulez pas perdre de temps en vérification inutile des liens à chaque itération lorsque votre forboucle était déjà configurée pour respecter les limites.
Matteo Italia
4
@ VF1 "de retour dans la journée", cela aurait pu être deux octets (DEC PDP / 11
ça vous tente
7
Ce n'est pas juste "de retour dans la journée". Pour le logiciel ciblé par C en tant que "langage d'assemblage portable", tel que les systèmes d'exploitation, les pilotes de périphériques, les logiciels embarqués temps réel, etc. Le gaspillage d'une demi-douzaine d'instructions lors de la vérification des limites est important et, dans de nombreux cas, vous devez être "hors limites" (comment pouvez-vous écrire un débogueur si vous ne pouvez pas accéder de manière aléatoire au stockage d'un autre programme?).
James Anderson
3
C'est en fait un argument plutôt faible, considérant que BCPL avait des arguments comptés en longueur. Tout comme Pascal, il se limitait généralement à un mot de 8 ou 9 bits, ce qui était un peu limitatif (cela exclut également la possibilité de partager des parties de chaînes, bien que cette optimisation soit probablement beaucoup trop avancée pour le moment). Et déclarer une chaîne comme une structure de longueur suivie du tableau n'aurait pas vraiment besoin d'un support spécial du compilateur ..
Voo
11

En C, tout sous-ensemble contigu d’un tableau est également un tableau et peut être utilisé comme tel. Ceci s’applique à la fois aux opérations de lecture et d’écriture. Cette propriété ne serait pas valable si la taille était explicitement stockée.

MSalters
la source
6
"Le design serait différent" n'est pas une raison pour que le design soit différent.
VF1
7
@ VF1: Avez-vous déjà programmé en Pascal standard? La capacité de C à être raisonnablement flexible avec les tableaux représentait une énorme amélioration par rapport à l'assemblage (aucune sécurité) et à la première génération de langages typesafe (sécurité excessive, avec des limites de tableau exactes)
MSalters
5
Cette capacité à découper un tableau est en effet un argument de poids pour la conception C89.
Les hackers Fortran de la vieille école ont également bon usage de cette propriété (bien que cela nécessite de passer la tranche à un tableau situé à Fortran). Déroutant et douloureux à programmer ou à déboguer, mais rapide et élégant en travaillant.
dmckee
3
Il existe une alternative de conception intéressante qui permet de découper: Ne stockez pas la longueur à côté des tableaux. Pour tout pointeur sur un tableau, stockez la longueur avec le pointeur. (Lorsque vous ne disposez que d'un véritable tableau C, la taille est une constante de temps de compilation et disponible pour le compilateur.) Elle prend plus d'espace, mais permet de découper tout en conservant la longueur. Rust fait cela pour les &[T]types, par exemple.
8

Le plus gros problème d'avoir des tableaux étiquetés avec leur longueur n'est pas tant l'espace requis pour stocker cette longueur, ni la question de savoir comment le stocker. Utiliser un octet supplémentaire pour les tableaux courts ne serait généralement pas inacceptable, ni quatre. des octets supplémentaires pour les tableaux longs, mais l’utilisation de quatre octets, même pour des tableaux courts, peut être). Un problème beaucoup plus important est celui du code suivant:

void ClearTwoElements(int *ptr)
{
  ptr[-2] = 0;
  ptr[2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo+2);
  ClearTwoElements(foo+7);
  ClearTwoElements(foo+1);
  ClearTwoElements(foo+8);
}

la seule façon pour ce code d'accepter le premier appel ClearTwoElementsmais de rejeter le second serait que le ClearTwoElementsprocédé reçoive une information suffisante pour savoir qu'il recevait dans chaque cas une référence à une partie du tableau, fooen plus de savoir quelle partie. Cela doublerait généralement le coût de passage des paramètres de pointeur. En outre, si chaque tableau était précédé d'un pointeur sur une adresse située juste après la fin (le format le plus efficace pour la validation), un code optimisé ClearTwoElementsdeviendrait probablement le suivant:

void ClearTwoElements(int *ptr)
{
  int* array_end = ARRAY_END(ptr);
  if ((array_end - ARRAY_BASE(ptr)) < 10 ||
      (ARRAY_BASE(ptr)+4) <= ADDRESS(ptr) ||          
      (array_end - 4) < ADDRESS(ptr)))
    trap();
  *(ADDRESS(ptr) - 4) = 0;
  *(ADDRESS(ptr) + 4) = 0;
}

Notez qu'un appelant de méthode peut, en général, parfaitement légitimement passer un pointeur au début du tableau ou au dernier élément d'une méthode; si la méthode tente d'accéder à des éléments qui sortent du tableau passé, de tels pointeurs pourraient causer des problèmes. Par conséquent, une méthode appelée devrait d’abord s’assurer que le tableau était suffisamment grand pour que l’arithmétique du pointeur permettant de valider ses arguments ne soit pas elle-même hors limites, puis effectuer quelques calculs de pointeur pour valider les arguments. Le temps consacré à cette validation dépasserait probablement le coût consacré à un travail réel. En outre, la méthode pourrait probablement être plus efficace si elle était écrite et appelée:

void ClearTwoElements(int arr[], int index)
{
  arr[index-2] = 0;
  arr[index+2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo,2);
  ClearTwoElements(foo,7);
  ClearTwoElements(foo,1);
  ClearTwoElements(foo,8);
}

Le concept d'un type qui combine quelque chose qui identifie un objet avec quelque chose qui identifie un morceau de celui-ci est bon. Un pointeur de style C est cependant plus rapide s'il n'est pas nécessaire d'effectuer une validation.

supercat
la source
Si les tableaux avaient une taille d'exécution, le pointeur sur tableau serait fondamentalement différent du pointeur sur un élément de tableau. Ce dernier pourrait ne pas être directement convertible en ancien (sans créer un nouveau tableau). []La syntaxe peut toujours exister pour les pointeurs, mais elle serait différente de celle de ces tableaux "réels" hypothétiques et le problème que vous décrivez n'existerait probablement pas.
Hyde
@hyde: La question est de savoir si l'arithmétique devrait être autorisée sur les pointeurs dont l'adresse de base de l'objet est inconnue. De plus, j'ai oublié une autre difficulté: les tableaux au sein de structures. En y réfléchissant, je ne suis pas sûr qu’il y aurait un type de pointeur pouvant pointer vers un tableau stocké dans une structure, sans exiger que chaque pointeur inclue non seulement l’adresse du pointeur lui-même, mais également les informations légales supérieure et inférieure. plages auxquelles il peut accéder.
Supercat
Point de croisement. Je pense que cela reste néanmoins la réponse d’Amon.
VF1
La question concerne les tableaux. Le pointeur est une adresse mémoire et ne changera pas avec le principe de la question, dans la mesure où il comprend l'intention. Les tableaux auraient une longueur, les pointeurs seraient inchangés (sauf que le pointeur sur tableau aurait besoin d'un nouveau type, distinct, unique, un peu comme le pointeur sur la structure).
Hyde
@hyde: Si l'on modifie suffisamment la sémantique de la langue, il est possible que les tableaux incluent une longueur associée, bien que les tableaux stockés dans des structures posent quelques difficultés. Avec la sémantique actuelle, la vérification des limites de tableaux ne serait utile que si cette même vérification s’appliquait aux pointeurs d’éléments de tableau.
Supercat
7

L'une des différences fondamentales entre C et la plupart des langages de 3ème génération, et tous les langages plus récents dont je suis conscient, est que C n'a pas été conçu pour simplifier la vie du programmeur, ni pour la rendre plus sûre. Il a été conçu dans l’espoir que le programmeur sache ce qu’il fait et veut faire exactement et seulement cela. Il ne fait rien "en coulisse", vous n'avez donc aucune surprise. Même l'optimisation au niveau du compilateur est facultative (sauf si vous utilisez un compilateur Microsoft).

Si un programmeur veut écrire des limites en vérifiant son code, C le simplifie, mais le programmeur doit choisir de payer le prix correspondant en termes d'espace, de complexité et de performances. Même si je ne l'utilisais pas dans la colère depuis de nombreuses années, je l'utilise quand même pour enseigner la programmation afin de faire comprendre le concept de prise de décision basée sur des contraintes. En gros, cela signifie que vous pouvez choisir de faire tout ce que vous voulez, mais chaque décision que vous prenez a un prix que vous devez connaître. Cela devient encore plus important lorsque vous commencez à dire aux autres ce que vous voulez que leurs programmes fassent.

Paul Smith
la source
3
C n'était pas tellement "conçu" qu'il a évolué. À l'origine, une déclaration comme int f[5];ne créerait pas fun tableau de cinq éléments; au lieu de cela, c'était équivalent à int CANT_ACCESS_BY_NAME[5]; int *f = CANT_ACCESS_BY_NAME;. L'ancienne déclaration pouvait être traitée sans que le compilateur doive vraiment "comprendre" les temps des tableaux; il lui fallait simplement sortir une directive assembleur pour allouer de l'espace et ensuite oublier que cela fn'avait jamais rien à voir avec un tableau. Les comportements incohérents des types de tableaux proviennent de cela.
Supercat
1
Il s'avère qu'aucun programmeur ne sait ce qu'il fait dans la mesure où C l'exige.
CodesInChaos
7

Réponse courte:

C étant un langage de programmation de bas niveau , il s’attend à ce que vous vous occupiez de ces problèmes vous-même, mais cela ajoute une plus grande souplesse dans la façon dont vous l’implémentez.

C a le concept de compilation d’un tableau qui est initialisé avec une longueur, mais au moment de l’exécution, le tout est simplement stocké sous la forme d’un pointeur unique indiquant le début des données. Si vous voulez transmettre la longueur du tableau à une fonction avec le tableau, vous le faites vous-même:

retval = my_func(my_array, my_array_length);

Ou vous pouvez utiliser une structure avec un pointeur et une longueur, ou toute autre solution.

Un langage de niveau supérieur le ferait pour vous dans le cadre de son type de tableau. En C, on vous confie la responsabilité de le faire vous-même, mais vous avez également la possibilité de choisir comment le faire. Et si tout le code que vous écrivez connaît déjà la longueur du tableau, vous n'avez pas besoin de la transmettre comme variable.

L’inconvénient évident est qu’en l’absence de limites inhérentes à la vérification des tableaux transmis en tant que pointeurs, vous pouvez créer un code dangereux, mais c’est la nature des langages de bas niveau / systèmes et le compromis qu’ils donnent.

thomasrutter
la source
1
+1 "Et si tout le code que vous écrivez connaît déjà la longueur du tableau, vous n'avez pas besoin de transmettre la longueur comme variable."
Si seulement la structure pointeur + longueur avait été intégrée dans le langage et la bibliothèque standard. Tant de failles de sécurité auraient pu être évitées.
CodesInChaos
Alors ce ne serait pas vraiment C. Il y a d'autres langues qui font ça. C vous obtient le bas niveau.
thomasrutter
C a été inventé en tant que langage de programmation de bas niveau, et de nombreux dialectes supportent toujours la programmation de bas niveau, mais de nombreux rédacteurs de compilateurs préfèrent les dialectes qui ne peuvent pas vraiment être appelés langages de bas niveau. Ils autorisent et exigent même une syntaxe de bas niveau, mais tentent ensuite d'inférer des constructions de niveau supérieur dont le comportement peut ne pas correspondre à la sémantique implicite de la syntaxe.
Supercat
5

Le problème du stockage supplémentaire est un problème, mais à mon avis un problème mineur. Après tout, la plupart du temps, vous aurez besoin de suivre la longueur de toute façon, même si un argument précis a été avancé: elle peut souvent être suivie de manière statique.

Un problème plus important est de savoir stocker la longueur et combien de temps pour la fabriquer. Il n'y a pas un seul endroit qui fonctionne dans toutes les situations. Vous pourriez dire simplement stocker la longueur dans la mémoire juste avant les données. Que se passe-t-il si le tableau ne pointe pas vers la mémoire, mais quelque chose comme un tampon UART?

Le fait de ne pas laisser de longueur permet au programmeur de créer ses propres abstractions pour la situation appropriée et de nombreuses bibliothèques prêtes à l'emploi sont disponibles pour le cas d'utilisation générale. La vraie question est de savoir pourquoi ces abstractions ne sont pas utilisées dans des applications sensibles à la sécurité.

Karl Bielefeldt
la source
1
You might say just store the length in the memory just before the data. What if the array isn't pointing to memory, but something like a UART buffer?Pourriez-vous s'il vous plaît expliquer un peu plus? Aussi ce quelque chose qui pourrait arriver trop souvent ou c'est juste un cas rare?
Mahdi
Si je l'avais conçu, un argument de fonction écrit T[]ne serait pas équivalent à T*mais passerait plutôt un tuple de pointeur et de taille à la fonction. Les tableaux de taille fixe pourraient se décomposer en une telle tranche, au lieu de se transformer en pointeurs comme en C. Le principal avantage de cette approche n'est pas qu'elle est sûre en soi, mais c'est une convention sur laquelle tout, y compris la bibliothèque standard construire.
CodesInChaos
1

Du développement du langage C :

Les structures, semble-t-il, devaient correspondre de manière intuitive à la mémoire de la machine, mais dans une structure contenant un tableau, il n'y avait pas de bonne place pour cacher le pointeur contenant la base du tableau, ni aucun moyen pratique de l'arranger. initialisé. Par exemple, les entrées de répertoire des anciens systèmes Unix peuvent être décrites en C comme suit:
struct {
    int inumber;
    char    name[14];
};
Je voulais que la structure ne caractérise pas simplement un objet abstrait, mais décrive également une collection de bits pouvant être lus dans un répertoire. Où le compilateur pourrait-il cacher le pointeur sur namecelui demandé par la sémantique? Même si on pensait de manière plus abstraite aux structures et si l’espace pour les pointeurs pouvait être caché, comment puis-je régler le problème technique de l’initialisation correcte de ces pointeurs lors de l’allocation d’un objet compliqué, par exemple des structures contenant des tableaux contenant des structures à une profondeur arbitraire?

La solution a constitué le saut crucial dans la chaîne évolutive entre BCPL sans typage et C typé. Elle a éliminé la matérialisation du pointeur dans le stockage et a plutôt provoqué la création du pointeur lorsque le nom du tableau est mentionné dans une expression. La règle, qui subsiste dans le C actuel, est que les valeurs de type tableau sont converties, lorsqu'elles apparaissent dans des expressions, en pointeurs sur le premier des objets constituant le tableau.

Ce passage explique pourquoi les expressions de tableau décroissent en pointeurs dans la plupart des cas, mais le même raisonnement s'applique à la raison pour laquelle la longueur du tableau n'est pas stockée avec le tableau lui-même; si vous voulez un mappage un-à-un entre la définition de type et sa représentation en mémoire (comme Ritchie l'a fait), il n'y a pas de bon endroit pour stocker ces métadonnées.

Pensez également aux tableaux multidimensionnels. où stockeriez-vous les métadonnées de longueur pour chaque dimension de sorte que vous puissiez toujours parcourir le tableau avec quelque chose comme

T *p = &a[0][0];

for ( size_t i = 0; i < rows; i++ )
  for ( size_t j = 0; j < cols; j++ )
    do_something_with( *p++ );
John Bode
la source
-2

La question suppose qu'il y a des tableaux en C. Il n'y en a pas. Les choses appelées tableaux ne sont qu'un sucre syntaxique pour les opérations sur des séquences continues de données et l'arithmétique de pointeur.

Le code suivant copie certaines données de src vers dst sous forme de fragments de taille int ne sachant pas qu'il s'agit en fait d'une chaîne de caractères.

char src[] = "Hello, world";
char dst[1024];
int *my_array = src; /* What? Compiler warning, but the code is valid. */
int *other_array = dst;
int i;
for (i = 0; i <= sizeof(src)/sizeof(int); i++)
    other_array[i] = my_array[i]; /* Oh well, we've copied some extra bytes */
printf("%s\n", dst);

Pourquoi C est tellement simplifié qu'il n'a pas de tableaux appropriés? Je ne sais pas réponse correcte à cette nouvelle question. Mais certaines personnes disent souvent que C est juste un assembleur (un peu) plus lisible et portable.

aragaer
la source
2
Je ne pense pas que vous ayez répondu à la question.
Robert Harvey
2
Ce que vous avez dit est vrai, mais la personne qui pose la question veut savoir pourquoi .
9
Rappelez-vous que l'un des surnoms de C est "assemblage portable". Bien que les nouvelles versions de la norme aient ajouté des concepts de niveau supérieur, elle se compose essentiellement de constructions et d'instructions simples de bas niveau communes à la plupart des machines non triviales. Cela détermine la plupart des décisions de conception prises dans la langue. Les seules variables qui existent à l'exécution sont les entiers, les flottants et les pointeurs. Les instructions incluent l'arithmétique, les comparaisons et les sauts. Presque tout le reste est constitué d'une couche mince.
8
C'est faux de dire que C n'a pas de tableaux, vu que vous ne pouvez vraiment pas générer le même binaire avec d'autres constructions (enfin, du moins pas si vous envisagez d'utiliser #defines pour déterminer la taille des tableaux). Les tableaux en C sont des "séquences continues de données", rien de sucré à ce sujet. Utiliser des pointeurs comme s'ils étaient des tableaux est le sucre syntaxique ici (au lieu de l'arithmétique de pointeur explicite), pas les tableaux eux-mêmes.
Hyde
2
Oui, pensez à ce code: struct Foo { int arr[10]; }. arrest un tableau, pas un pointeur.
Gort the Robot Le