Question Avec les tableaux, pourquoi est-ce qu'un [5] == 5 [a]?


Comme Joel le fait remarquer Stack Overflow podcast # 34, dans C Langage de programmation (aka: K & R), il y a une mention de cette propriété des tableaux dans C: a[5] == 5[a]

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


1427
2017-12-19 17:01


origine


Réponses:


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

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

Donc 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 qui est 5 éléments plus loin de a, qui est le même que *(a + 5), et des mathématiques de l'école primaire, nous savons que ceux-ci sont égaux (l'addition est commutatif).


1704
2017-12-19 17:04



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


273
2017-12-19 17:05



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 du [] opérateur) est équivalent à i[p].

(Et en 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 types numériques différents qui sont promus à un type commun, la commutativité prend tout son sens: x + y == y + x.

Mais dans ce cas, nous parlons spécifiquement de l'arithmétique du 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 un non-sens.)

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

Pour l'addition, les deux opérandes doivent avoir un type arithmétique, ou un   opérande doit être un pointeur vers un type d'objet complet et l'autre   doit avoir un type entier.

Il aurait tout aussi bien pu dire:

Pour l'addition, les deux opérandes doivent avoir un type arithmétique, ou la gauche   opérande doit être un pointeur vers un type d'objet complet et le opérande droit   doit avoir un type entier.

auquel cas les deux i + p et i[p] serait illégal.

En termes C ++, nous avons vraiment deux ensembles de surcharge + opérateurs, qui peuvent être vaguement décrits comme:

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 comme ça?

C ++ a hérité cette définition de C, qui l'a obtenue de B (la commutativité de l'indexation de tableau est explicitement mentionnée dans le 1972 Référence des utilisateurs à B), qui l'a obtenu BCPL (manuel daté de 1967), qui pourrait bien l'avoir obtenu de langues encore plus anciennes (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 langages ancêtres de C.

Ces langues étaient beaucoup moins typées que le C moderne. En particulier, la distinction entre pointeurs et entiers était souvent ignorée. (Les programmeurs début C utilisaient parfois des pointeurs comme entiers non signés, avant unsigned mot-clé a été ajouté à la langue.) Donc l'idée de rendre l'addition non-commutative parce que les opérandes sont de types différents n'aurait probablement pas eu lieu aux concepteurs de ces langages. Si un utilisateur voulait ajouter deux "choses", que ces "choses" soient des entiers, des pointeurs, ou quelque chose d'autre, ce n'était pas à la langue de l'empêcher.

Et au fil des années, tout changement à cette règle aurait brisé 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 sur la gauche et l'entier sur la droite pourrait casser un code existant, mais il n'y aurait aucune perte de puissance expressive réelle.

Alors maintenant nous avons arr[3] et 3[arr] signifiant exactement la même chose, bien que la dernière forme ne devrait jamais apparaître en dehors du IOCCC.


186
2017-08-23 01:37



Et, bien sûr

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

La raison principale de ceci était que dans les années 70 quand C a été conçu, les ordinateurs n'avaient pas beaucoup de mémoire (64 Ko était beaucoup), donc le compilateur C n'a pas fait beaucoup de vérification syntaxique. Par conséquent "X[Y]"était plutôt aveuglément traduit en"*(X+Y)"

Cela explique aussi le "+=" et "++"syntaxes. Tout dans 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 devait (A += C). De même, si C était 1, 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 le reconnaissait pas. (Plus récemment, les compilateurs font, donc ces syntaxes sont largement inutiles ces jours-ci)


184
2017-12-19 17:07



Une chose que personne ne semble avoir mentionnée au sujet du problème de Dinah sizeof:

Vous pouvez uniquement ajouter un entier à un pointeur, vous ne pouvez pas ajouter deux pointeurs ensemble. De cette façon, lorsque vous ajoutez un pointeur à un entier ou un entier à un pointeur, le compilateur sait toujours quel bit a une taille à prendre en compte.


51
2018-02-11 15:56



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

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

imprime

false

46
2017-08-11 13:50



Bonne question / réponses.

Je veux juste souligner que les pointeurs C et les tableaux 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 une 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 du tableau.


23
2017-12-20 08:16



Je viens de découvrir que cette syntaxe laide pourrait être "utile", ou au moins très amusante à jouer quand vous voulez faire face à un tableau d'index qui se réfèrent à 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 tout à fait sûr qu'il n'y a pas de cas d'utilisation pour cela en code réel, mais je l'ai trouvé intéressant de toute façon :)


21
2018-06-10 19:50



Pour les pointeurs en C, nous avons

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

et aussi

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

D'où il est vrai que a[5] == 5[a].


17
2018-03-23 07:05



Pas une réponse, mais juste un peu de matière à réflexion. Si la classe a un opérateur index / indice surchargé, 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 classe, cela ne peut pas être fait:

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

14
2018-06-19 08:37



Il a une très bonne explication UN TUTORIAL SUR LES POINTEURS ET LES ARRAYS EN C par Ted Jensen.

Ted Jensen l'a expliqué comme:

En fait, c'est vrai, c'est-à-dire partout où l'on écrit a[i] ça peut être   remplacé par *(a + i)  sans aucun problème. En fait, le compilateur   va créer le même code dans les deux cas. Nous voyons donc ce pointeur   arithmétique est la même chose que l'indexation de tableau. L'une ou l'autre syntaxe produit   le même résultat.

Cela ne veut pas dire que les pointeurs et les tableaux   sont la même chose, ils ne le sont pas. Nous disons seulement que pour identifier   un élément donné d'un tableau, nous avons le choix de deux syntaxes, un   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, une partie de celui-ci .. (a + i), est un ajout simple utilisant le +   opérateur et les règles de C indiquent qu'une telle expression est   commutatif. C'est (a + i) est identique à (i + a). Ainsi nous pourrions   écrire *(i + a) aussi facilement que *(a + i).   Mais *(i + a) aurait pu venir de i[a] ! De tout cela vient le curieux   vérité que si:

char a[20];

l'écriture

a[3] = 'x';

est le même que l'écriture

3[a] = 'x';

9
2017-09-27 06:46