web-dev-qa-db-fra.com

Comment fonctionne le code C qui imprime de 1 à 1000 sans boucles ni instructions conditionnelles?

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);
}
148
obo

N'écrivez jamais de code comme ça.


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).

264
Mat

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).

41
tdammers