Comme le souligne Joel dans le podcast Stack Overflow # 34 , en langage de programmation C (alias: K & R), il est fait mention de cette propriété des tableaux en C:a[5] == 5[a]
Joel dit que c'est à cause de l'arithmétique des pointeurs mais je ne comprends toujours pas. Pourquoia[5] == 5[a]
?
a[1]
comme une série de jetons, pas de chaînes: * ({emplacement entier de} un {opérateur} + {entier} 1) est identique à * ({entier} 1 {opérateur} + {emplacement entier de} a) mais n'est pas la même chose que * ({emplacement entier} d'un {opérateur} + {opérateur} +)char bar[]; int foo[];
etfoo[i][bar]
est utilisé comme expression.a[b]
=*(a + b)
pour tout donnéa
etb
, mais c'était le libre choix des concepteurs de langage pour+
être défini commutatif pour tous les types. Rien ne pouvait les empêcher d'interdirei + p
en autorisantp + i
.+
à être commutatif, alors peut-être que le vrai problème est de choisir des opérations de pointeur ressemblant à de l'arithmétique, au lieu de concevoir un opérateur de décalage séparé.Réponses:
La norme C définit l'
[]
opérateur comme suit:a[b] == *(a + b)
Évaluera donc
a[5]
:et
5[a]
évaluera:a
est un pointeur sur le premier élément du tableau.a[5]
est la valeur qui est éloignée de 5 élémentsa
, ce qui est le même que*(a + 5)
, et des mathématiques du primaire, nous savons que ceux-ci sont égaux (l'addition est commutative ).la source
a[5]
sera compilé en quelque chose commemov eax, [ebx+20]
au lieu de[ebx+5]
*(10 + (int *)13) != *((int *)10 + 13)
. En d'autres termes, il se passe plus ici que l'arithmétique au primaire. La commutativité repose de manière critique sur le compilateur qui reconnaît quel opérande est un pointeur (et à quelle taille d'objet). Pour le dire autrement(1 apple + 2 oranges) = (2 oranges + 1 apple)
, mais(1 apple + 2 oranges) != (1 orange + 2 apples)
.Parce que l'accès aux tableaux est défini en termes de pointeurs.
a[i]
est défini comme signifiant*(a + i)
, qui est commutatif.la source
*(i + a)
, ce qui peut s'écrirei[a]
".*(a + i)
est commutatif". Cependant,*(a + i) = *(i + a) = i[a]
parce que l' addition est commutative.Je pense que quelque chose manque aux autres réponses.
Oui,
p[i]
est par définition équivalent à*(p+i)
, qui (parce que l'addition est commutative) est équivalent à*(i+p)
, qui (encore une fois, par la définition de l'[]
opérateur) est équivalent ài[p]
.(Et dans
array[i]
, le nom du tableau est implicitement converti en un pointeur vers le premier élément du tableau.)Mais la commutativité de l'addition n'est pas si évidente dans ce cas.
Lorsque les deux opérandes sont du même type, ou même de différents types numériques qui sont promus à un type commun, commutativité est parfaitement logique:
x + y == y + x
.Mais dans ce cas, nous parlons spécifiquement de l'arithmétique des pointeurs, où un opérande est un pointeur et l'autre est un entier. (Entier + entier est une opération différente et pointeur + pointeur est un non-sens.)
La description de l'
+
opérateur par la norme C ( N1570 6.5.6) dit:Il aurait tout aussi bien pu dire:
auquel cas les deux
i + p
eti[p]
seraient illégaux.En termes C ++, nous avons vraiment deux ensembles d'
+
opérateurs surchargés , qui peuvent être décrits de manière générale comme:et
dont seul le premier est vraiment nécessaire.
Alors pourquoi est-ce ainsi?
C ++ a hérité de cette définition de C, qui l'a obtenue de B (la commutativité de l'indexation de tableaux est explicitement mentionnée dans la référence des utilisateurs de 1972 à B ), qui l'a obtenue de BCPL (manuel daté de 1967), qui pourrait bien l'avoir obtenue de même langues antérieures (CPL? Algol?).
Ainsi, l'idée que l'indexation de tableaux est définie en termes d'addition, et que l'addition, même d'un pointeur et d'un entier, est commutative, remonte à plusieurs décennies, aux langages des ancêtres de C.
Ces langages étaient beaucoup moins fortement typés que le C moderne. En particulier, la distinction entre pointeurs et entiers a souvent été ignorée. (Les premiers programmeurs C utilisaient parfois des pointeurs comme des entiers non signés, avant que le
unsigned
mot - clé ne soit ajouté au langage.) Ainsi, l'idée de rendre l'addition non commutative parce que les opérandes sont de types différents n'aurait probablement pas surgi pour les concepteurs de ces langages. Si un utilisateur voulait ajouter deux "choses", que ces "choses" soient des entiers, des pointeurs ou autre chose, ce n'était pas à la langue de l'empêcher.Et au fil des ans, toute modification de cette règle aurait violé le code existant (bien que la norme ANSI C de 1989 ait pu être une bonne opportunité).
Changer C et / ou C ++ pour exiger de placer le pointeur à gauche et l'entier à droite pourrait casser du code existant, mais il n'y aurait aucune perte de puissance expressive réelle.
Nous avons donc maintenant
arr[3]
et3[arr]
signifiant exactement la même chose, bien que cette dernière forme ne devrait jamais apparaître en dehors de l' IOCCC .la source
3[arr]
c'est un artefact intéressant mais devrait rarement, voire jamais, être utilisé. La réponse acceptée à cette question (< stackoverflow.com/q/1390365/356> ) que j'ai posée il y a quelque temps a changé ma façon de penser la syntaxe. Bien qu'il n'y ait souvent techniquement pas une bonne et une mauvaise façon de faire ces choses, ces types de fonctionnalités vous font penser d'une manière distincte des détails de mise en œuvre. Il y a des avantages à cette façon de penser différente qui est en partie perdue lorsque vous fixez les détails de la mise en œuvre.ring16_t
qui contient 65535 produirait unring16_t
avec une valeur 1, indépendamment de la taille deint
.Et bien sûr
La principale raison en était que dans les années 70, lorsque C a été conçu, les ordinateurs n'avaient pas beaucoup de mémoire (64 Ko, c'était beaucoup), donc le compilateur C ne faisait pas beaucoup de vérification de la syntaxe. Par conséquent, "
X[Y]
" a été traduit aveuglément en "*(X+Y)
"Cela explique également les syntaxes "
+=
" et "++
". Tout sous la forme "A = B + C
" avait la même forme compilée. Mais, si B était le même objet que A, alors une optimisation au niveau de l'assemblage était disponible. Mais le compilateur n'était pas assez brillant pour le reconnaître, donc le développeur a dû (A += C
). De même, siC
c'était le cas1
, une optimisation de niveau d'assemblage différente était disponible, et encore une fois, le développeur devait la rendre explicite, car le compilateur ne l'a pas reconnue. (Plus récemment, les compilateurs le font, donc ces syntaxes sont largement inutiles de nos jours)la source
Personne ne semble avoir mentionné le problème de Dinah avec
sizeof
:Vous ne pouvez ajouter qu'un entier à un pointeur, vous ne pouvez pas ajouter deux pointeurs ensemble. De cette façon, lors de l'ajout d'un pointeur à un entier ou d'un entier à un pointeur, le compilateur sait toujours quel bit a une taille qui doit être prise en compte.
la source
Pour répondre à la question littéralement. Il n'est pas toujours vrai que
x == x
impressions
la source
cout << (a[5] == a[5] ? "true" : "false") << endl;
estfalse
.x == x
n'est pas toujours vrai). Je pense que c'était son intention. Il est donc techniquement correct (et peut-être, comme on dit, le meilleur type de correct!).NAN
in<math.h>
, ce qui est mieux que0.0/0.0
, car0.0/0.0
UB__STDC_IEC_559__
n'est pas défini (la plupart des implémentations ne définissent pas__STDC_IEC_559__
, mais sur la plupart des implémentations0.0/0.0
fonctionneront toujours)Je viens de découvrir que cette syntaxe laide pourrait être "utile", ou du moins très amusante à jouer avec un tableau d'index qui fait référence à des positions dans le même tableau. Il peut remplacer les crochets imbriqués et rendre le code plus lisible!
Bien sûr, je suis sûr qu'il n'y a pas de cas d'utilisation pour cela dans du vrai code, mais je l'ai quand même trouvé intéressant :)
la source
i[a][a][a]
vous pensez que i est soit un pointeur vers un tableau ou un tableau d'un pointeur vers un tableau ou un tableau ... eta
est un index. Quand vous voyeza[a[a[i]]]
, vous pensez que a est un pointeur vers un tableau ou un tableau eti
est un index.Belles questions / réponses.
Je veux juste souligner que les pointeurs et les tableaux C ne sont pas les mêmes , bien que dans ce cas la différence ne soit pas essentielle.
Considérez les déclarations suivantes:
Dans
a.out
, le symbole sea
trouve à une adresse qui est le début du tableau, et le symbole sep
trouve à une adresse où un pointeur est stocké, et la valeur du pointeur à cet emplacement de mémoire est le début du tableau.la source
int a[10]
était un pointeur appelé «a», qui indiquait suffisamment de mémoire pour 10 entiers, ailleurs. Ainsi, a + i et j + i avaient la même forme: ajouter le contenu de quelques emplacements mémoire. En fait, je pense que BCPL était sans type, donc ils étaient identiques. Et la mise à l'échelle du type de taille ne s'appliquait pas, car BCPL était purement orienté sur les mots (sur les machines à adressage de mots également).int*p = a;
àint b = 5;
Dans ce dernier, "b" et "5" sont tous deux des entiers, mais "b" est une variable, tandis que "5" est une valeur fixe. De même, "p" et "a" sont les deux adresses d'un caractère, mais "a" est une valeur fixe.Pour les pointeurs en C, nous avons
et aussi
Il est donc vrai que
a[5] == 5[a].
la source
Pas une réponse, mais juste quelques pistes de réflexion. Si la classe a un opérateur d'index / indice surchargé, l'expression
0[x]
ne fonctionnera pas:Puisque nous n'avons pas accès à la classe int , cela ne peut pas être fait:
la source
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
operator[]
doit être une fonction membre non statique avec exactement un paramètre." Je connaissais cette restrictionoperator=
, je ne pensais pas qu'elle s'appliquait[]
.[]
opérateur, ce ne sera plus jamais équivalent ... sia[b]
est égal à*(a + b)
et vous changez cela, vous devrez également surchargerint::operator[](const Sub&);
et ceint
n'est pas une classe ...Il a une très bonne explication dans A TUTORIAL ON POINTERS AND ARRAYS IN C de Ted Jensen.
Ted Jensen l'a expliqué comme suit:
la source
Je sais qu'on a répondu à la question, mais je n'ai pas pu résister à partager cette explication.
Je me souviens des principes de conception du compilateur, supposons
a
qu'unint
tableau ait une taille deint
2 octets et que l'adresse de basea
soit 1000.Comment ça
a[5]
va marcher ->Donc,
De même, lorsque le code c est décomposé en code à 3 adresses,
5[a]
deviendra ->Donc , fondamentalement , les deux déclarations pointent vers le même emplacement en mémoire et , par conséquent,
a[5] = 5[a]
.Cette explication est également la raison pour laquelle les indices négatifs dans les tableaux fonctionnent en C.
c'est-à-dire que si j'y accède,
a[-5]
cela me donneraIl me rendra l'objet à l'emplacement 990.
la source
Dans les tableaux C ,
arr[3]
et3[arr]
sont les mêmes, et leurs notations de pointeur équivalentes sont*(arr + 3)
à*(3 + arr)
. Mais au contraire[arr]3
ou[3]arr
n'est pas correct et entraînera une erreur de syntaxe, car(arr + 3)*
et(3 + arr)*
ne sont pas des expressions valides. La raison en est que l'opérateur de déréférencement doit être placé avant l'adresse fournie par l'expression, et non après l'adresse.la source
dans le compilateur c
sont différentes façons de faire référence à un élément d'un tableau! (PAS DU tout bizarre)
la source
Un peu d'histoire maintenant. Entre autres langages, BCPL a eu une influence assez importante sur le développement précoce de C. Si vous avez déclaré un tableau dans BCPL avec quelque chose comme:
qui allouait en fait 11 mots de mémoire, pas 10. Typiquement, V était le premier et contenait l'adresse du mot immédiatement suivant. Donc, contrairement à C, nommer V est allé à cet endroit et a pris l'adresse du zéro élément du tableau. Par conséquent, l'indirection de tableau dans BCPL, exprimée en
vraiment à faire
J = !(V + 5)
(en utilisant la syntaxe BCPL) car il était nécessaire de récupérer V pour obtenir l'adresse de base du tableau. AinsiV!5
et5!V
étaient synonymes. Comme observation anecdotique, le WAFL (Warwick Functional Language) a été écrit en BCPL, et au mieux de ma mémoire avait tendance à utiliser cette dernière syntaxe plutôt que la première pour accéder aux nœuds utilisés comme stockage de données. Certes, cela remonte à environ 35 à 40 ans, donc ma mémoire est un peu rouillée. :)L'innovation de supprimer le mot de stockage supplémentaire et de demander au compilateur d'insérer l'adresse de base de la baie quand il a été nommé est venue plus tard. Selon le journal d'histoire C, cela s'est produit à peu près au moment où les structures ont été ajoutées à C.
Notez que
!
dans BCPL était à la fois un opérateur de préfixe unaire et un opérateur d'infixe binaire, dans les deux cas faisant de l'indirection. juste que la forme binaire incluait un ajout des deux opérandes avant de faire l'indirection. Étant donné la nature orientée mot de BCPL (et B), cela avait en fait beaucoup de sens. La restriction du "pointeur et entier" a été rendue nécessaire en C quand il a gagné des types de données, etsizeof
est devenue une chose.la source
Eh bien, c'est une fonctionnalité qui n'est possible qu'en raison de la prise en charge des langues.
Le compilateur interprète
a[i]
comme*(a+i)
et l'expression est5[a]
évaluée*(5+a)
. Puisque l'addition est commutative, il s'avère que les deux sont égaux. Par conséquent, l'expression est évaluée àtrue
.la source
En C
Le pointeur est une "variable"
le nom du tableau est un "mnémonique" ou un "synonyme"
p++;
est valide maisa++
n'est pas validea[2]
est égal à 2 [a] parce que le fonctionnement interne sur les deux est"Arithmétique du pointeur" calculée en interne comme
*(a+3)
équivaut à*(3+a)
la source
types de pointeurs
1) pointeur vers les données
2) pointeur const vers les données
3) pointeur const vers les données const
et les tableaux sont de type (2) de notre liste
Lorsque vous définissez un tableau à la fois, une adresse est initialisée dans ce pointeur
Comme nous savons que nous ne pouvons pas changer ou modifier la valeur de const dans notre programme car cela déclenche une ERREUR lors de la compilation temps
La principale différence que j'ai trouvée est ...
On peut réinitialiser le pointeur par une adresse mais pas le même cas avec un tableau.
======
et revenons à votre question ...
a[5]
n'est rien mais*(a + 5)
vous pouvez facilement comprendre en
a
- contenant l'adresse (les gens l'appellent comme adresse de base) tout comme un type de pointeur (2) dans notre liste[]
- cet opérateur peut être remplaçable par un pointeur*
.donc finalement ...
la source