web-dev-qa-db-fra.com

Impossible de comprendre cette façon de calculer le carré d'un nombre

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?

135
Emanuel

É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
117
Mark Lakata

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.

C11: 6.5.6 Opérateurs additifs (p9):

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 expressions P et Q pointent respectivement vers i- th et j -th éléments d'un objet tableau, l'expression (P)-(Q) a la valeur i−j à condition que la valeur tienne dans un objet de type ptrdiff_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):

enter image description here

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 nint. Par conséquent, le total des éléments du tableau est n*n = n2.


REMARQUE:

C11: 6.5.6 Opérateurs additifs (p9):

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.

86
haccks

a est un tableau (variable) de nint.

&a est un pointeur vers un tableau (variable) de nint.

(&a)[1] est un pointeur de int un int après le dernier élément du tableau. Ce pointeur est nint é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 * nint é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 * nint é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).

35
ouah

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.

12
Vlad from Moscow
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,

  1. type de (&a)[n] est int[n] pointeur
  2. type de a est int pointeur

Maintenant, 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
4
onlyice