55 votes

Syntaxe de tableau vs syntaxe de pointeur et génération de code?

Dans le livre, "Comprendre et Utiliser C les Pointeurs" par Richard Reese, il dit à la page 85,

int vector[5] = {1, 2, 3, 4, 5};

Le code généré par vector[i] est différent du code généré par *(vector+i) . La notation vector[i] génère du code machine qui commence à l'emplacement vecteur , se déplace i des postes à partir de cet emplacement, et se sert de son contenu. La notation *(vector+i) génère du code machine qui commence à l'emplacement de vector , ajoute i pour l'adresse, puis utilise le contenu à cette adresse. Alors que le résultat est le même, à la génération de code machine est différente. Cette différence est rarement d'importance pour la plupart des programmeurs.

Vous pouvez voir l' extrait ici. Qu'est-ce passage signifie? Dans ce contexte serait un compilateur de générer un code différent pour ces deux-là? Est-il une différence entre "se déplacer" à partir de la base, et "ajouter" à la base? Je n'ai pu obtenir que cela fonctionne sur GCC -- générant différents du code machine.

97voto

Matt McNabb Points 14273

La citation est juste fausse. Il est assez tragique que de telles ordures soient toujours publiées au cours de cette décennie. En fait, la norme C définit x[y] comme *(x+y) .

La partie sur lvalues plus loin sur la page est complètement et complètement fausse aussi.

À mon humble avis, la meilleure façon d'utiliser ce livre est de le mettre dans un bac de recyclage ou de le brûler.

33voto

Antti Haapala Points 11542

J'ai 2 C fichiers: ex1.c

% cat ex1.c
#include <stdio.h>

int main (void) {
    int vector[5] = { 1, 2, 3, 4, 5 };
    printf("%d\n", vector[3]);
}

et ex2.c,

% cat ex2.c
#include <stdio.h>

int main (void) {
    int vector[5] = { 1, 2, 3, 4, 5 };
    printf("%d\n", *(vector + 3));
}

Et je compile à la fois dans l'assemblée, et de montrer la différence dans le code assembleur généré

% gcc -S ex1.c; gcc -S ex2.c; diff -u ex1.s ex2.s
--- ex1.s       2018-07-17 08:19:25.425826813 +0300
+++ ex2.s       2018-07-17 08:19:25.441826756 +0300
@@ -1,4 +1,4 @@
-       .file   "ex1.c"
+       .file   "ex2.c"
        .text
        .section        .rodata
 .LC0:

Q. E. D.


La norme C très explicitement les états (C11 n1570 6.5.2.1p2):

  1. Un postfix expression suivie par une expression entre crochets [] est un indice de la désignation d'un élément d'un tableau d'objet. La définition de l'indice de l'opérateur [] que E1[E2] est identique à (*((E1)+(E2))). En raison de la conversion des règles qui s'appliquent à l'binaire + de l'opérateur, si E1 est un tableau d'objet (de manière équivalente, un pointeur vers le premier élément d'un tableau d'objet) et E2 est un entier, E1[E2] désigne l' E2-ème élément de la E1 (compter à partir de zéro).

En outre, le comme-si la règle s'applique ici - si le comportement du programme est le même, le compilateur peut générer le même code, même si la sémantique n'étaient pas les mêmes.

19voto

Steve Summit Points 16971

Le passage cité est tout à fait tort. Les expressions vector[i] et *(vector+i) sont parfaitement identiques et peuvent s'attendre à générer un code identique dans toutes les circonstances.

Les expressions vector[i] et *(vector+i) sont identiques , par définition. C'est une propriété fondamentale du langage de programmation C. Compétente programmeur C comprend cela. Tout auteur d'un livre intitulé" Comprendre et Utiliser C les Pointeurs doivent comprendre cela. Tout auteur d'un compilateur C le comprendrez. Les deux fragments de générer un code identique n'est pas par hasard, mais parce que pratiquement n'importe quel compilateur C sera, en effet, traduire l'une dans l'autre presque immédiatement, de sorte que le temps qu'il arrive à son code phase de génération, il ne sait même pas ce qui avait été initialement utilisé. (Je serais assez surpris si un compilateur C jamais eu de code différent pour vector[i] plutôt *(vector+i).)

Et en fait, le texte cité contredit lui-même. Comme vous l'avez remarqué, les deux passages

La notation vector[i] génère du code machine qui commence à l'emplacement de vector , se déplace i des postes à partir de cet emplacement, et se sert de son contenu.

et

La notation *(vector+i) génère du code machine qui commence à l'emplacement de vector, ajoute i pour l'adresse, puis utilise le contenu à cette adresse.

dire essentiellement la même chose.

Sa langue est étrangement similaire à celui en question 6.2 de l'ancien C FAQ de la liste:

...lorsque le compilateur voit l'expression a[3], il émet le code pour démarrer à l'emplacement "a", déplacez les trois derniers, et de récupérer le personnage il. Lorsqu'il voit l'expression p[3], il émet le code pour démarrer à l'emplacement "p", de récupérer la valeur du pointeur, d'ajouter trois à l'aiguille, et finalement de récupérer le caractère souligné.

Mais bien sûr, la principale différence ici est que l' a est un tableau et p est un pointeur. La FAQ de la liste est parlent pas de a[3] contre *(a+3), mais plutôt sur l' a[3] (ou *(a+3)) où a est un tableau, par rapport p[3] (ou *(p+3)) où p est un pointeur. (Bien sûr ces deux cas, de générer un code différent, car les tableaux et les pointeurs sont différents. Comme la liste FAQ explique, la récupération d'une adresse d'un pointeur de variable est fondamentalement différent de l'utilisation de l'adresse d'un tableau.)

6voto

Hanno Binder Points 3880

Je pense que le texte original peut être fait référence à certaines optimisations qui certains compilateur peut ou ne peut pas effectuer.

Exemple:

for ( int i = 0; i < 5; i++ ) {
  vector[i] = something;
}

vs

for ( int i = 0; i < 5; i++ ) {
  *(vector+i) = something;
}

Dans le premier cas, un compilateur optimisant peut détecter que le tableau vector est itéré élément par élément et ainsi de générer quelque chose comme

void* tempPtr = vector;
for ( int i = 0; i < 5; i++ ) {
  *((int*)tempPtr) = something;
  tempPtr += sizeof(int); // _move_ the pointer; simple addition of a constant.
}

Il pourrait même être en mesure d'utiliser le PROCESSEUR cible du pointeur de la post-incrémentation des instructions disponibles.

Pour le second cas, il est "plus difficile" pour le compilateur de voir que l' adresse qui est calculée par le biais de certains "arbitraire" l'arithmétique de pointeur expression montre la même propriété de façon monotone faire avancer un montant fixe à chaque itération. Elle peut donc pas trouver de l'optimisation et de calculer ((void*)vector+i*sizeof(int)) dans chaque itération, qui utilise un supplémentaire de multiplication. Dans ce cas, il n'y a pas (temporaire) d'un pointeur qui est "déplacé", mais seulement une adresse temporaire re-calculé.

Toutefois, la déclaration n'a probablement pas universellement valables pour tous les compilateurs C dans toutes les versions.

Mise à jour:

J'ai vérifié l'exemple ci-dessus. Il semble que sans les optimisations activées au moins gcc-8.1 x86-64 génère plus de code (2 instructions supplémentaires) pour le second (pointeur-arithmethics) la forme que sur le premier (index de tableau).

Voir: https://godbolt.org/g/7DaPHG

Cependant, avec toutes les optimisations activée sur (-O...-O3), le code généré est le même (longueur) pour les deux.

6voto

supercat Points 25534

La Norme spécifie le comportement de l' arr[i] lorsque arr est un tableau d'objet comme étant équivalent à la décomposition arr à un pointeur, l'ajout d' i, et de déférence le résultat. Bien que les comportements seraient équivalentes dans tous les cas précis, il y a certains cas où les compilateurs des actions du processus utilement, même si le Standard a besoin de lui, et le traitement de l' arrayLvalue[i] et *(arrayLvalue+i) peut varier en conséquence.

Par exemple, étant donné

char arr[5][5];
union { unsigned short h[4]; unsigned int w[2]; } u;

int atest1(int i, int j)
{
if (arr[1][i])
    arr[0][j]++;
return arr[1][i];
}
int atest2(int i, int j)
{
if (*(arr[1]+i))
    *((arr[0])+j)+=1;
return *(arr[1]+i);
}
int utest1(int i, int j)
{
    if (u.h[i])
        u.w[j]=1;
    return u.h[i];
}
int utest2(int i, int j)
{
    if (*(u.h+i))
        *(u.w+j)=1;
    return *(u.h+i);
}

GCC du code généré pour test1 supposerons que l'arr[1][i] et arr[0][j] ne peut pas alias, mais le code généré pour test2 permettra de l'arithmétique des pointeurs pour accéder à l'ensemble de la matrice, Sur le revers de la médaille, gcc reconnaît que dans utest1, lvalue les expressions de u.h[i] et u.w[j] à la fois accéder à la même union, mais il n'est pas assez sophistiquée pour avis le même sujet *(u.h+i) et *(u.w+j) dans utest2.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X