J'ai trouvé une fonction qui calcule le carré d'un nombre:
int p(int n) {
int a[n]; //works on C99 and above
return (&a)[n] - a;
}
Il renvoie la valeur de n2. La question est, comment cela fait-il? Après quelques tests, j'ai trouvé qu'entre (&a)[k]
Et (&a)[k+1]
Se trouve sizeof(a)
/sizeof(int)
. Pourquoi donc?
Évidemment un hack ... mais un moyen de quadrature d'un nombre sans utiliser le *
opérateur (c'était une exigence du concours de codage).
(&a)[n]
équivaut à un pointeur sur int
à l'emplacement
(a + sizeof(a[n])*n)
et donc toute l'expression est
(&a)[n] -a
= (a + sizeof(a[n])*n -a) /sizeof(int)
= sizeof(a[n])*n / sizeof(int)
= sizeof(int) * n * n / sizeof(int)
= n * n
Pour comprendre ce hack, vous devez d'abord comprendre la différence de pointeur, c'est-à-dire ce qui se passe lorsque deux pointeurs pointant vers des éléments de même tableau sont soustraits?
Lorsqu'un pointeur est soustrait d'un autre, le résultat est la distance (mesurée en éléments de tableau) entre les pointeurs. Donc, si p
pointe vers a[i]
et q
pointe vers a[j]
, puis p - q
est égal à i - j
.
Lorsque deux pointeurs sont soustraits , les deux pointent vers des éléments du même objet tableau, ou un au-delà du dernier élément de l'objet tableau; le résultat est la différence des indices des deux éléments du tableau . [...].
En d'autres termes, si les expressionsP
etQ
pointent respectivement versi
- th etj
-th éléments d'un objet tableau, l'expression(P)-(Q)
a la valeuri−j
à condition que la valeur tienne dans un objet de typeptrdiff_t
.
Maintenant, je m'attends à ce que vous soyez au courant de la conversion du nom du tableau en pointeur, a
convertit en pointeur vers le premier élément du tableau a
. &a
est l'adresse de l'ensemble du bloc de mémoire, c'est-à-dire qu'il s'agit d'une adresse de tableau a
. La figure ci-dessous vous aidera à comprendre (lire cette réponse pour une explication détaillée):
Cela vous aidera à comprendre pourquoi a
et &a
a la même adresse et comment (&a)[i]
est l'adresse de ie tableau (de même taille que celui de a
).
Donc, la déclaration
return (&a)[n] - a;
est équivalent à
return (&a)[n] - (&a)[0];
et cette différence donnera le nombre d'éléments entre les pointeurs (&a)[n]
et (&a)[0]
, qui sont des tableaux n
chacun des éléments n
int
. Par conséquent, le total des éléments du tableau est n*n
= n
2.
REMARQUE:
Lorsque deux pointeurs sont soustraits, les deux doivent pointer vers des éléments du même objet tableau, ou un au-delà du dernier élément de l'objet tableau ; le résultat est la différence des indices des deux éléments du tableau. La taille du résultat est définie par l'implémentation, et son type (un type entier signé) est
ptrdiff_t
défini dans le<stddef.h>
entête. Si le résultat n'est pas représentable dans un objet de ce type, le comportement n'est pas défini.
Puisque (&a)[n]
ne pointe ni sur les éléments du même objet tableau ni sur le dernier élément de l'objet tableau, (&a)[n] - a
invoquera un comportement indéfini .
Notez également qu'il vaut mieux changer le type de retour de la fonction p
en ptrdiff_t
.
a
est un tableau (variable) de n
int
.
&a
est un pointeur vers un tableau (variable) de n
int
.
(&a)[1]
est un pointeur de int
un int
après le dernier élément du tableau. Ce pointeur est n
int
éléments après &a[0]
.
(&a)[2]
est un pointeur de int
un int
devant le dernier élément de tableau de deux tableaux. Ce pointeur est 2 * n
int
éléments après &a[0]
.
(&a)[n]
est un pointeur de int
un int
devant le dernier élément du tableau de n
tableaux. Ce pointeur est n * n
int
éléments après &a[0]
. Il suffit de soustraire &a[0]
ou a
et vous avez n
.
Bien sûr, il s'agit d'un comportement techniquement indéfini même s'il fonctionne sur votre machine en tant que (&a)[n]
ne pointe pas à l'intérieur du tableau ou au-delà du dernier élément du tableau (comme l'exigent les règles C de l'arithmétique des pointeurs).
Si vous avez deux pointeurs qui pointent vers deux éléments du même tableau, sa différence donnera le nombre d'éléments entre ces pointeurs. Par exemple, cet extrait de code affichera 2.
int a[10];
int *p1 = &a[1];
int *p2 = &a[3];
printf( "%d\n", p2 - p1 );
Considérons maintenant l'expression
(&a)[n] - a;
Dans cette expression, a
a le type int *
Et pointe vers son premier élément.
L'expression &a
A le type int ( * )[n]
et pointe vers la première ligne du tableau bidimensionnel imagé. Sa valeur correspond à la valeur de a
bien que les types soient différents.
( &a )[n]
est le nième élément de ce tableau bidimensionnel imagé et a le type int[n]
C'est-à-dire qu'il s'agit de la nième ligne du tableau imagé. Dans l'expression (&a)[n] - a
, Il est converti à l'adresse de son premier élément et a le type `int *.
Ainsi, entre (&a)[n]
Et a
, il y a n lignes de n éléments. La différence sera donc égale à n * n
.
Expression | Value | Explanation
a | a | point to array of int elements
a[n] | a + n*sizeof(int) | refer to n-th element in array of int elements
-------------------------------------------------------------------------------------------------
&a | a | point to array of (n int elements array)
(&a)[n] | a + n*sizeof(int[n]) | refer to n-th element in array of (n int elements array)
-------------------------------------------------------------------------------------------------
sizeof(int[n]) | n * sizeof(int) | int[n] is a type of n-int-element array
Ainsi,
(&a)[n]
est int[n]
pointeura
est int
pointeurMaintenant, l'expression (&a)[n]-a
effectue une soustraction de pointeur:
(&a)[n]-a
= ((a + n*sizeof(int[n])) - a) / sizeof(int)
= (n * sizeof(int[n])) / sizeof(int)
= (n * n * sizeof(int)) / sizeof(int)
= n * n