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:
- Avoir la longueur disponible dans un tampon peut empêcher un dépassement de tampon.
- Un style Java
arr.length
est à la fois clair et évite au programmeur de devoir garder plusieursint
s sur la pile s’il utilise plusieurs tableaux - 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.
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.Réponses:
Les tableaux C gardent une trace de leur longueur, car la longueur du tableau est une propriété statique:
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_LENGTH
pour 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.
la source
sizeof(xs)
oùxs
est 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. Sisizeof(xs)
oùxs
est un tableau est différent desizeof(xs)
oùxs
est un pointeur, ce n'est pas surprenant, car vous comparez des pommes avec des oranges .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:
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.
la source
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 :-)À 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: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.
la source
for
boucle était déjà configurée pour respecter les limites.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.
la source
&[T]
types, par exemple.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:
la seule façon pour ce code d'accepter le premier appel
ClearTwoElements
mais de rejeter le second serait que leClearTwoElements
procédé reçoive une information suffisante pour savoir qu'il recevait dans chaque cas une référence à une partie du tableau,foo
en 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éClearTwoElements
deviendrait probablement le suivant: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:
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.
la source
[]
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.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.
la source
int f[5];
ne créerait pasf
un 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 celaf
n'avait jamais rien à voir avec un tableau. Les comportements incohérents des types de tableaux proviennent de cela.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:
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.
la source
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 où 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é.
la source
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?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.Du développement du langage C :
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
la source
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.
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.
la source
struct Foo { int arr[10]; }
.arr
est un tableau, pas un pointeur.