Récréation

En illustration sur les bizarreries des tableaux dans le langage C, voici la contribution de David Korn (le créateur du korn shell) à la compétition du code C le plus obscur (IOCCC) de 1987 :
main() { printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}

Non, ce programme n'imprime pas have fun with unix ou quelque chose de ce genre ! Le lecteur est invité à essayer d'élucider ce programme (oui, il imprime quelque chose, mais quoi ?) la solution est donnée à la page suivante.

Voici les clés de la compréhension :

  1. Tout compilateur C possède un certain nombre de constantes prédéfinies dépendant de l'architecture de la machine sur laquelle il s'exécute. En particulier, tout compilateur pour machine UNIX prédéfinit la constante unix avec comme valeur 1. Donc le programme est équivalent à :
    main() { printf(&1["\021%six\012\0"],(1)["have"]+"fun"-0x60);}
    

  2. Si on se souvient qu'on a vu en [*] que pour un tableau t, t[i] est équivalent à i[t], on voit que 1["\021%six\012\0"] est équivalent à "\021%six\012\0"[1] et (1)["have"] à "have"[1]. Donc &1["\021%six\012\0"] est l'adresse du caractère % dans la chaîne "\021%six\012\0" et "have"[1] est le caractère 'a'. On peut donc réécrire le programme :
    main() { printf("%six\012\0", 'a' + "fun" -0x60);}
    

  3. La fin d'une chaîne est signalée par un caractère null (\0) et le compilateur en met un à la fin de chaque chaîne littérale. Celui qui est ici est donc inutile. D'autre part, il existe une notation plus parlante pour le caractère de code \012 (c'est à dire new line), il s'agit de la notation \n. Le programme peut donc se réécrire :
    main() { printf("%six\n", 'a' + "fun" -0x60);}
    

  4. Le caractère 'a' a pour code ASCII 0x61, donc 'a' -0x60 est égal à 1. Réécrivons le programme :
    main() { printf("%six\n","fun" + 1); }
    

  5. "fun" + 1 est l'adresse du caractère u dans la chaîne "fun", le programme devient donc :
    main() { printf("%six\n","un"); }
    
    il imprime donc unix.

Matthieu Moy 2017-02-08