web-dev-qa-db-fra.com

Avec les tableaux, pourquoi un [5] == 5 [a]?

Comme le souligne Joel dans podcast Stack Overflow n ° 34 , dans langage de programmation C (alias: K & R), il est fait mention de cette propriété des tableaux dans C: a[5] == 5[a]

Joel dit que c'est à cause de l'arithmétique de pointeur mais je ne comprends toujours pas. Pourquoi a[5] == 5[a]?

1491
Dinah

La norme C définit l’opérateur [] comme suit:

a[b] == *(a + b)

Par conséquent, a[5] évaluera:

*(a + 5)

et 5[a] évaluera à:

*(5 + a)

a est un pointeur sur le premier élément du tableau. a[5] est la valeur correspondant à 5 éléments plus éloignés de a, identique à *(a + 5), et aux mathématiques de l'école élémentaire sais que ceux-ci sont égaux (l'addition est commutative ).

1837
Mehrdad Afshari

Parce que l'accès au tableau est défini en termes de pointeurs. a[i] signifie *(a + i), qui est commutatif.

277
David Thornley

Je pense que quelque chose manque aux autres réponses.

Oui, p[i] est par définition équivalent à *(p+i), ce qui (parce que l’addition est commutative) est équivalent à *(i+p), ce 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 sur 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, voire de types numériques différents, promus en un type commun, la commutativité a tout son sens: x + y == y + x.

Mais dans ce cas, nous parlons spécifiquement d'arithmétique de pointeur, 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 absurde.)

La description de l'opérateur + ( N1570 6.5.6) par la norme C dit:

Pour plus, les deux opérandes doivent avoir un type arithmétique, ou un L’opérande doit être un pointeur sur un type d’objet complet et l’autre doit être de type entier.

Cela aurait tout aussi bien pu dire:

Pour plus, les deux opérandes doivent avoir un type arithmétique, ou the left L'opérande doit être un pointeur sur un type d'objet complet et sur l'opérande right doit être de type entier.

dans ce cas, i + p et i[p] seraient illégaux.

En termes C++, nous avons en réalité deux ensembles d'opérateurs + surchargés, qui peuvent être décrits de manière vague:

pointer operator+(pointer p, integer i);

et

pointer operator+(integer i, pointer p);

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 sur des tableaux est explicitement mentionnée dans la référence 1972 Utilisateurs à B ), qui l'a obtenue de BCPL (manuel de 1967 ), qui l’a peut-être déjà été dans des langues antérieures (CPL? Algol?).

Donc, l'idée que l'indexation de tableau 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 langues des ancêtres de C.

Ces langues étaient beaucoup moins typées que le C moderne. En particulier, la distinction entre les pointeurs et les entiers était souvent ignorée. (Les premiers programmeurs C utilisaient parfois les pointeurs sous forme d'entiers non signés, avant que le mot clé unsigned ne soit ajouté au langage.) L'idée de rendre l'addition non commutative car les opérandes sont de types différents n'aurait probablement pas été envisagée par les concepteurs de ces langages. . Si un utilisateur souhaitait ajouter deux "éléments", qu'il s'agisse de nombres entiers, de pointeurs ou autre, le choix de la langue ne le permettait pas.

Et au fil des ans, toute modification de cette règle aurait enfreint le code existant (bien que la norme ANSI C de 1989 aurait pu être une bonne occasion).

Changer C et/ou C++ pour obliger à placer le pointeur à gauche et l’entier à droite peut casser du code existant, mais il n’y aurait pas de perte de pouvoir expressif réel.

Nous avons donc maintenant arr[3] et 3[arr] qui signifient exactement la même chose, bien que cette dernière forme ne devrait jamais apparaître en dehors de IOCCC .

201
Keith Thompson

Et bien sur

 ("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')

La raison principale en était que dans les années 70, lorsque C était conçu, les ordinateurs ne disposaient pas de beaucoup de mémoire (64 Ko était beaucoup), donc le compilateur C ne faisait pas beaucoup de vérification de syntaxe. Par conséquent, "X[Y]" a été traduit aveuglément par "*(X+Y)

Cela explique également les syntaxes "+=" et "++". Tout dans la forme "A = B + C" avait la même forme compilée. Mais si B était le même objet que A, une optimisation au niveau de l’Assemblée était disponible. Mais le compilateur n'était pas assez brillant pour le reconnaître, le développeur devait donc (A += C). De même, si C était 1, une optimisation de niveau Assembly différente était disponible, et le développeur devait à nouveau le rendre explicite, car le compilateur ne le reconnaissait pas. (Plus récemment, les compilateurs le font, ces syntaxes sont donc largement inutiles de nos jours)

190
James Curran

Une chose que personne ne semble avoir mentionnée à propos du problème de Dinah avec sizeof:

Vous pouvez uniquement ajouter un entier à un pointeur, vous ne pouvez pas ajouter deux pointeurs ensemble. Ainsi, lors de l'ajout d'un pointeur sur un entier ou d'un entier à un pointeur, le compilateur sait toujours quel bit a une taille à prendre en compte.

52
user30364

Pour répondre à la question littéralement. Ce n'est pas toujours vrai que x == x

double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;

empreintes

false
48
Peter Lawrey

Belle question/réponses.

Je veux juste souligner que les pointeurs et les tableaux C ne sont pas les même , bien que dans ce cas la différence ne soit pas essentielle. 

Considérez les déclarations suivantes:

int a[10];
int* p = a;

Dans a.out , le symbole a est à une adresse qui est le début du tableau et le symbole p est à une adresse où un pointeur est stocké et la valeur du pointeur à cet emplacement de mémoire est le début de la matrice. 

23
PolyThinker

Je viens de découvrir que cette syntaxe laide pourrait être "utile", ou du moins très amusante à jouer lorsque vous souhaitez gérer un tableau d'index qui font référence à des positions dans le même tableau. Il peut remplacer les crochets imbriqués et rendre le code plus lisible! 

int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a;  //  s == 5

for(int i = 0 ; i < s ; ++i) {  

           cout << a[a[a[i]]] << endl;
           // ... is equivalent to ... 
           cout << i[a][a][a] << endl;  // but I prefer this one, it's easier to increase the level of indirection (without loop)

}

Bien sûr, je suis à peu près sûr qu'il n'y a pas de cas d'utilisation pour cela dans le code réel, mais je l'ai trouvé intéressant quand même :)

21

Pour les pointeurs en C, nous avons

a[5] == *(a + 5)

et aussi

5[a] == *(5 + a)

Il est donc vrai que a[5] == 5[a].

17
user1287577

Pas une réponse, mais juste quelques pistes de réflexion ... Si la classe a un opérateur d'index/indice en surcharge, l'expression 0[x] ne fonctionnera pas:

class Sub
{
public:
    int operator [](size_t nIndex)
    {
        return 0;
    }   
};

int main()
{
    Sub s;
    s[0];
    0[s]; // ERROR 
}

Puisque nous n’avons pas accès à int class, cela ne peut pas être fait:

class int
{
   int operator[](const Sub&);
};
14
Ajay

Il a une très bonne explication dans Un tutoriel sur les pointeurs et les tableaux dans C .__ de Ted Jensen.

Ted Jensen l'a expliqué comme suit:

En fait, c’est vrai, c’est-à-dire partout où on écrit a[i], cela peut être remplacé par *(a + i) sans aucun problème. En fait, le compilateur créera le même code dans les deux cas. Ainsi, nous voyons ce pointeur l'arithmétique est la même chose que l'indexation par tableau. Soit la syntaxe produit le même résultat.

Ce n'est pas dire que les pointeurs et les tableaux sont la même chose, ils ne sont pas. Nous disons seulement que pour identifier un élément donné d'un tableau, nous avons le choix entre deux syntaxes, une en utilisant l'indexation de tableau et l'autre en utilisant l'arithmétique de pointeur, qui donner des résultats identiques.

Maintenant, en regardant ce dernier expression, qui en fait partie. (a + i) est un simple ajout utilisant le + opérateur et les règles de C stipulent qu'une telle expression est commutatif. C'est-à-dire que (a + i) est identique à (i + a). Ainsi, nous pourrions écrivez *(i + a) aussi facilement que *(a + i). Mais *(i + a) aurait pu provenir de i[a]! De tout cela vient le curieux vérité que si:

char a[20];

l'écriture

a[3] = 'x';

est la même chose que l'écriture

3[a] = 'x';
9
A.s. Bhullar

Je sais que la question a reçu une réponse, mais je n'ai pas pu m'empêcher de partager cette explication.

Je me souviens de Principles of Compiler Design, Supposons que a soit un tableau int et que la taille de int soit de 2 octets, & L'adresse de base pour a est de 1000.

Comment a[5] fonctionnera ->

Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

Alors, 

De même, lorsque le code c est divisé en code à 3 adresses, 5[a] deviendra ->

Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010 

Donc, fondamentalement, les deux instructions 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 index négatifs dans les tableaux fonctionnent en C.

c'est-à-dire que si j'accède à a[-5] cela me donnera

Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

Il me retournera objet à l'emplacement 990.

6
Ajinkya Patil

Dans C, les tableaux , arr[3] et 3[arr] sont identiques et leurs notations de pointeur équivalentes sont *(arr + 3) à *(3 + arr). 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érence doit être placé avant l'adresse générée par l'expression, pas après l'adresse.

5
Krishan

en c compilateur 

a[i]
i[a]
*(a+i)

sont différentes façons de se référer à un élément dans un tableau! (PAS AT TOUS WEIRD)

4
AVIK DUTTA

Un peu d'histoire maintenant. Parmi les autres langues, 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:

let V = vec 10

qui allouait en réalité 11 mots de mémoire, et non 10. En règle générale, V était le premier et contenait l'adresse du mot immédiatement suivant. Donc, contrairement à C, le nom de V est allé à cet endroit et a ramassé l'adresse de l'élément zeroeth du tableau. Par conséquent, indirection de tableau dans BCPL, exprimée sous la forme

let J = V!5

J = !(V + 5) (à l’aide de la syntaxe BCPL), car il fallait extraire V pour obtenir l’adresse de base du tableau. Ainsi, V!5 et 5!V étaient synonymes. Comme observation anecdotique, WAFL (Warwick Functional Language) a été écrit en BCPL et, autant que je sache, 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 à quelque 35 à 40 ans, alors ma mémoire est un peu rouillée. :)

L'innovation consistant à se passer du mot de stockage supplémentaire et à ce que le compilateur insère l'adresse de base du tableau lorsqu'il a été nommé est venue plus tard. Selon le document sur l'histoire de 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, exécutant dans les deux cas indirection. juste que la forme binaire incluait une addition des deux opérandes avant de faire l'indirection. Compte tenu de la nature orientée Word de BCPL (et B), cela avait beaucoup de sens. La restriction de "pointeur et entier" devenait nécessaire en C quand il gagnait des types de données et sizeof devenait chose.

1
dgnuff

En C 

 int a[]={10,20,30,40,50};
 int *p=a;
 printf("%d\n",*p++);//output will be 10
 printf("%d\n",*a++);//will give an error

Le pointeur est une "variable" 

le nom du tableau est un "mnémonique" ou un "synonyme"

p++; est valide mais a++ est invalide

a[2] est égal à 2 [a] car le fonctionnement interne sur les deux est 

"Arithmétique de pointeur" calculé en interne comme

*(a+3) est égal à *(3+a)

0
Jayghosh Wankar

Eh bien, c’est une fonctionnalité qui n’est possible que grâce au support linguistique.

Le compilateur interprète a[i] en tant que *(a+i) et l'expression 5[a] est évaluée à *(5+a). Comme l'addition est commutative, il s'avère que les deux sont égaux. Par conséquent, l'expression est évaluée à true.

0
Harsha J K