J'ai trouvé C
code qui imprime de 1 à 1000 sans boucles ni conditions : Mais je ne comprends pas comment cela fonctionne. Quelqu'un peut-il parcourir le code et expliquer chaque ligne?
#include <stdio.h>
#include <stdlib.h>
void main(int j) {
printf("%d\n", j);
(&main + (&exit - &main)*(j/1000))(j+1);
}
Pour j<1000
, j/1000
Vaut zéro (division entière). Donc:
(&main + (&exit - &main)*(j/1000))(j+1);
est équivalent à:
(&main + (&exit - &main)*0)(j+1);
Lequel est:
(&main)(j+1);
Qui appelle main
avec j+1
.
Si j == 1000
, Alors les mêmes lignes apparaissent comme:
(&main + (&exit - &main)*1)(j+1);
Qui se résume à
(&exit)(j+1);
Qui est exit(j+1)
et quitte le programme.
(&exit)(j+1)
Et exit(j+1)
sont essentiellement la même chose - citant C99 §6.3.2.1/4:
Un désignateur de fonction est une expression qui a un type de fonction. Sauf lorsqu'il s'agit de l'opérande de l'opérateur sizeof ou de l'opérateur unaire & , un désignateur de fonction de type "fonction retournant le type "est converti en une expression qui a le type" pointeur vers la fonction retournant le type ".
exit
est un désignateur de fonction. Même sans l'opérateur d'adresse unaire &
, Il est traité comme un pointeur vers la fonction. (Le &
Le rend explicite.)
Et les appels de fonction sont décrits au §6.5.2.2/1 et suivants:
L'expression qui dénote la fonction appelée doit avoir le type pointeur vers la fonction retournant void ou retournant un type d'objet autre qu'un type de tableau.
Ainsi, exit(j+1)
fonctionne en raison de la conversion automatique du type de fonction en type pointeur-fonction, et (&exit)(j+1)
Fonctionne également avec une conversion explicite en type pointeur-fonction.
Cela étant dit, le code ci-dessus n'est pas conforme (main
prend deux arguments ou aucun), et &exit - &main
Est, je crois, indéfini selon le §6.5.6/9:
Lorsque deux pointeurs sont soustraits, les deux doivent pointer vers des éléments du même objet tablea, ou un après le dernier élément de l'objet tableau; ...
L'addition (&main + ...)
Serait valable en soi, et pourrait être utilisée, si la quantité ajoutée était nulle, puisque §6.5.6/7 dit:
Pour les besoins de ces opérateurs, un pointeur sur un objet qui n'est pas un élément d'un tableau se comporte de la même manière qu'un pointeur sur le premier élément d'un tableau de longueur un avec le type de l'objet comme type d'élément.
Donc, ajouter zéro à &main
Serait correct (mais pas très utile).
Il utilise la récursivité, l'arithmétique des pointeurs et exploite le comportement d'arrondi de la division entière.
Le terme j/1000
Arrondit à 0 pour tous j < 1000
; une fois que j
atteint 1000, il est évalué à 1.
Maintenant, si vous avez a + (b - a) * n
, où n
est soit 0 soit 1, vous vous retrouvez avec a
si n == 0
Et b
si n == 1
. En utilisant &main
(L'adresse de main()
) et &exit
Pour a
et b
, le terme (&main + (&exit - &main) * (j/1000))
Renvoie &main
Lorsque j
est inférieur à 1000, &exit
Sinon. Le pointeur de fonction résultant reçoit alors l'argument j+1
.
Cette construction entière se traduit par un comportement récursif: tandis que j
est inférieur à 1000, main
s'appelle récursivement; lorsque j
atteint 1000, il appelle à la place exit
, ce qui rend le programme exit avec le code de sortie 1001 (ce qui est un peu sale, mais fonctionne).