En parcourant 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é cette réponse, mais la syntaxe diffère un peu, c'est-à-dire la méthode d'indexation
size = (&arr)[1] - arr;
Je pense donc que les deux questions sont valables et ont une approche légèrement différente du problème. Merci à tous pour votre aide immense et votre explication approfondie!
c
arrays
size
language-lawyer
pointer-arithmetic
janojlic
la source
la source
&a + 1
ne pointe vers aucun objet valide, il est donc invalide.*((*(&array + 1)) - 1)
sûr d'utiliser pour obtenir le dernier élément d'un tableau automatique? . tl; dr*(&a + 1)
invoque Undefined Behvaior(ptr)[x]
c'est la même chose que*((ptr) + x)
.Réponses:
Lorsque vous ajoutez 1 à un pointeur, le résultat est l'emplacement de l'objet suivant dans une séquence d'objets de type pointé (c'est-à-dire un tableau). Si
p
pointe vers unint
objet, alorsp + 1
pointera vers le suivantint
dans une séquence. Sip
pointe vers un tableau à 5 éléments deint
(dans ce cas, l'expression&a
), alorsp + 1
pointera vers le tableau à 5 éléments suivant deint
dans une séquence.La soustraction de deux pointeurs (à condition qu'ils pointent tous les deux dans 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
renvoie l'adresse dea
et a le typeint (*)[5]
(pointeur vers un tableau à 5 éléments deint
). L'expression&a + 1
renvoie l'adresse du prochain tableau à 5 éléments de laint
suitea
, et a également le typeint (*)[5]
. L'expression*(&a + 1)
déréférence le résultat de&a + 1
, de telle sorte qu'elle donne l'adresse du premierint
suivant le dernier élément dea
, et a un typeint [5]
, qui dans ce contexte «se désintègre» en une expression de typeint *
.De même, l'expression
a
«se désintègre» en un pointeur vers le premier élément du tableau et a un typeint *
.Une image peut aider:
Il s'agit de deux vues du même stockage - sur la gauche, nous le visualisons comme une séquence de tableaux à 5 éléments
int
, tandis que sur la droite, nous le visualisons comme une séquence deint
. Je montre également les différentes expressions et leurs types.Attention, l'expression
*(&a + 1)
entraîne un comportement indéfini :C 2011 Online Draft , 6.5.6 / 9
la source
size = (int*)(&a + 1) - a;
ce code serait tout à fait valide? : oCette ligne est la plus importante:
Comme vous pouvez le voir, il prend d'abord l'adresse de
a
et y ajoute une. Ensuite, il déréférence ce pointeur et en soustrait la valeur d'originea
.L'arithmétique du pointeur en C provoque le renvoi du nombre d'éléments dans le tableau, ou
5
. L'ajout d'un et&a
est un pointeur vers le tableau suivant de 5int
s aprèsa
. Après cela, ce code déréférence le pointeur résultant et soustraita
(un type de tableau qui s'est désintégré en pointeur) de cela, en 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 unint
type et contient la valeur(int *)160
. Lorsque vous soustrayez un nombre dexyz
, C spécifie que le montant réel soustraitxyz
correspond à ce nombre multiplié par la taille du type vers lequel il pointe. Par exemple, si vous soustrayez5
dexyz
, la valeur dexyz
résultat seraitxyz - (sizeof(*xyz) * 5)
si l'arithmétique du pointeur ne s'applique pas.Comme
a
pour un tableau de5
int
types, la valeur résultante sera 5. Cependant, cela ne fonctionnera pas avec un pointeur, uniquement avec un tableau. Si vous essayez ceci avec un pointeur, le résultat sera toujours1
.Voici un petit exemple qui montre les adresses et comment cela n'est pas défini. Le côté gauche montre les adresses:
Cela signifie que le code soustrait
a
de&a[5]
(oua+5
), donne5
.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.
la source
Hmm, je soupçonne que c'est quelque chose qui n'aurait pas fonctionné dans les premiers jours de C. C'est intelligent cependant.
Faire les étapes une par une:
&a
obtient un pointeur vers un objet de type int [5]+1
obtient le prochain objet de ce type en supposant qu'il existe un tableau de ces*
convertit efficacement cette adresse en pointeur de type vers int-a
soustrait les deux pointeurs int, renvoyant le nombre d'instances int entre eux.Je ne suis pas sûr que ce soit tout à fait légal (en cela je veux dire la langue-avocat juridique - 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é en accédant à un autre tableau, bien qu'un tableau parent, il n'est donc pas réellement un pointeur dans le même tableau quea
. 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 de 1 élément, l'opération de déréférencement (*
) n'est pas "autorisée" sur ce pointeur synthétisé, même si elle 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écomposé en un pointeur beaucoup plus rapidement, donc il
*(&a+1)
pourrait ne renvoyer que l'adresse du pointeur suivant de type int **. Les définitions plus rigoureuses du C ++ moderne permettent définitivement au pointeur sur le type de tableau d'exister et de connaître la taille du tableau, et probablement les normes C ont emboîté le pas. Tout le code de fonction C ne prend que des pointeurs comme arguments, donc la différence technique visible est 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éteur peut implémenter un tableau 2D comme un 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 si cela fonctionnait, cela donnerait la mauvaise réponse.
Une autre faiblesse possible peut être que le compilateur C peut aligner le tableau externe. Imaginez s'il s'agissait d'un tableau de 5 caractères (
char arr[5]
), lorsque le programme s'exécute,&a+1
il invoque le comportement "tableau de tableau". Le compilateur peut décider qu'un tableau de tableau de 5 chars (char arr[][5]
) est en fait généré comme un tableau de tableau de 8 chars (char arr[][8]
), de sorte que le tableau externe s'aligne bien. Le code dont nous discutons indiquerait 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.la source
sizeof(array)/sizeof(array[0])
donne le nombre d'éléments dans un tableau.&a+1
est défini. Comme le note John Bollinger, ce*(&a+1)
n'est pas le cas, car il tente de déréférencer un objet qui n'existe pas.char [][5]
aschar arr[][8]
. Un tableau n'est que les objets répétés qu'il contient; il n'y a pas de rembourrage. De plus, cela casserait l'exemple (non normatif) 2 de C 2018 6.5.3.4 7, qui nous dit que nous pouvons calculer le nombre d'éléments dans un tableau avecsizeof array / sizeof array[0]
.