web-dev-qa-db-fra.com

Comment ce morceau de code détermine-t-il la taille du tableau sans utiliser sizeof ()?

En passant par quelques questions d'entrevue C, j'ai trouvé une question indiquant "Comment trouver la taille d'un tableau en C sans utiliser l'opérateur sizeof?", Avec la solution suivante. Cela fonctionne, mais je ne comprends pas pourquoi.

#include <stdio.h>

int main() {
    int a[] = {100, 200, 300, 400, 500};
    int size = 0;

    size = *(&a + 1) - a;
    printf("%d\n", size);

    return 0;
}

Comme prévu, il renvoie 5.

edit: les gens ont souligné this réponse, mais la syntaxe diffère un peu, c'est-à-dire la méthode d'indexation

size = (&arr)[1] - arr;

donc je crois que les deux questions sont valables et ont une approche légèrement différente du problème. Merci à tous pour l'immense aide et l'explication approfondie!

132
janojlic

Lorsque vous ajoutez 1 à un pointeur, le résultat est l'emplacement de l'objet suivant dans une séquence d'objets du type pointé (c'est-à-dire un tableau). Si p pointe vers un objet int, alors p + 1 Pointera vers le int suivant dans une séquence. Si p pointe vers un tableau à 5 éléments de int (dans ce cas, l'expression &a), Alors p + 1 Pointera vers le suivant Tableau à 5 éléments de int dans une séquence.

La soustraction de deux pointeurs (à condition qu'ils pointent tous deux vers le même objet de tableau, ou que l'un pointe un au-delà du dernier élément du tableau) donne le nombre d'objets (éléments de tableau) entre ces deux pointeurs.

L'expression &a Donne l'adresse de a et a le type int (*)[5] (pointeur vers un tableau à 5 éléments de int). L'expression &a + 1 Donne l'adresse du prochain tableau à 5 éléments de int suivant a, et a également le type int (*)[5]. L'expression *(&a + 1) déréférence le résultat de &a + 1, De telle sorte qu'elle donne l'adresse du premier int suivant le dernier élément de a, et a le type int [5], Qui dans ce contexte "se désintègre" en une expression de type int *.

De même, l'expression a "se désintègre" vers un pointeur sur le premier élément du tableau et a le type int *.

Une photo peut aider:

int [5]  int (*)[5]     int      int *

+---+                   +---+
|   | <- &a             |   | <- a
| - |                   +---+
|   |                   |   | <- a + 1
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+
|   | <- &a + 1         |   | <- *(&a + 1)
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+

Il s'agit de deux vues du même stockage - à gauche, nous le voyons comme une séquence de tableaux à 5 éléments de int, tandis qu'à droite, nous le voyons comme une séquence de int. Je montre également les différentes expressions et leurs types.

Attention, l'expression *(&a + 1) entraîne un comportement indéfini :

...
Si le résultat pointe au-delà du dernier élément de l'objet tableau, il ne doit pas être utilisé comme l'opérande d'un opérateur unaire * évalué.

C 2011 Online Draft , 6.5.6/9

133
John Bode

Cette ligne est de la plus haute importance:

size = *(&a + 1) - a;

Comme vous pouvez le voir, il prend d'abord l'adresse de a et en ajoute une. Ensuite, il déréférence ce pointeur et lui soustrait la valeur d'origine de a.

L'arithmétique du pointeur en C entraîne le renvoi du nombre d'éléments dans le tableau, ou 5. Ajouter un et &a Est un pointeur vers le tableau suivant de 5 ints après a. Après cela, ce code déréférence le pointeur résultant et soustrait a (un type de tableau qui s'est désintégré en un pointeur) de cela, donnant le nombre d'éléments dans le tableau.

Détails sur le fonctionnement de l'arithmétique des pointeurs:

Supposons que vous ayez un pointeur xyz qui pointe vers un type int et contient la valeur (int *)160. Lorsque vous soustrayez un nombre de xyz, C spécifie que le montant réel soustrait de xyz est ce nombre multiplié par la taille du type vers lequel il pointe. Par exemple, si vous soustrayez 5 De xyz, la valeur de xyz résultante serait xyz - (sizeof(*xyz) * 5) si l'arithmétique du pointeur ne s'applique pas.

Comme a est un tableau de types 5int, la valeur résultante sera 5. Cependant, cela ne fonctionnera pas avec un pointeur, seulement avec un tableau. Si vous essayez ceci avec un pointeur, le résultat sera toujours 1.

Voici un petit exemple qui montre les adresses et comment cela n'est pas défini. Le côté gauche montre les adresses:

a + 0 | [a[0]] | &a points to this
a + 1 | [a[1]]
a + 2 | [a[2]]
a + 3 | [a[3]]
a + 4 | [a[4]] | end of array
a + 5 | [a[5]] | &a+1 points to this; accessing past array when dereferenced

Cela signifie que le code soustrait a de &a[5] (Ou a+5), Donnant 5.

Notez que ce comportement n'est pas défini et ne doit en aucun cas être utilisé. Ne vous attendez pas à ce que ce comportement soit cohérent sur toutes les plates-formes et ne l'utilisez pas dans les programmes de production.

34
S.S. Anne

Hmm, je soupçonne que c'est quelque chose qui n'aurait pas fonctionné au début de C. C'est quand même intelligent.

Prendre les étapes une par une:

  • &a Obtient un pointeur sur un objet de type int [5]
  • +1 Obtient le prochain objet en supposant qu'il existe un tableau de ceux-ci
  • * Convertit efficacement cette adresse en pointeur de type en int
  • -a Soustrait les deux pointeurs int, renvoyant le nombre d'instances int entre eux.

Je ne suis pas sûr que ce soit complètement légal (dans ce cas, je veux dire juriste-langue - cela ne fonctionnera pas dans la pratique), étant donné certaines opérations de type en cours. Par exemple, vous n'êtes "autorisé" à soustraire deux pointeurs que lorsqu'ils pointent vers des éléments du même tableau. *(&a+1) a été synthétisée en accédant à un autre tableau, bien qu'un tableau parent, il ne s'agit donc pas en fait d'un pointeur dans le même tableau que a. De plus, alors que vous êtes autorisé à synthétiser un pointeur au-delà du dernier élément d'un tableau et que vous pouvez traiter n'importe quel objet comme un tableau à 1 élément, l'opération de déréférencement (*) N'est pas "autorisée" sur ce point. pointeur synthétisé, même s'il n'a aucun comportement dans ce cas!

Je soupçonne que dans les premiers jours de C (syntaxe K&R, n'importe qui?), Un tableau s'est désintégré en un pointeur beaucoup plus rapidement, de sorte que la *(&a+1) pourrait ne renvoyer que l'adresse du prochain pointeur de type int ** . Les définitions plus rigoureuses du C++ moderne permettent certainement au pointeur du type de tableau d'exister et de connaître la taille du tableau, et les normes C ont probablement emboîté le pas. Tout le code de fonction C ne prend que des pointeurs comme arguments, la différence technique visible est donc minime. Mais je ne fais que deviner ici.

Ce type de question de légalité détaillée s'applique généralement à un interpréteur C ou à un outil de type lint, plutôt qu'au code compilé. Un interprète peut implémenter un tableau 2D en tant que tableau de pointeurs vers des tableaux, car il y a une fonctionnalité d'exécution de moins à implémenter, auquel cas le déréférencement du +1 serait fatal, et même s'il fonctionnait, cela donnerait la mauvaise réponse.

Une autre faiblesse possible peut être que le compilateur C pourrait aligner le tableau externe. Imaginez s'il s'agissait d'un tableau de 5 caractères (char arr[5]), Lorsque le programme exécute &a+1 Il invoque le comportement "tableau de tableau". Le compilateur peut décider qu'un tableau de tableau de 5 caractères (char arr[][5]) Est réellement généré sous la forme d'un tableau de tableau de 8 caractères (char arr[][8]), De sorte que le tableau extérieur s'aligne bien. Le code dont nous discutons rapporterait maintenant la taille du tableau comme 8, et non 5. Je ne dis pas qu'un compilateur particulier ferait certainement cela, mais il le pourrait.

27
Gem Taylor