vendredi 20 juin 2014

printf() magic tricks and %n specifier

Si l'on sait que la fonction printf est puissante, sa puissance est rarement utilisée. Sans doute la syntaxe de spécification du format n'est-elle pas très agréable. C'est vrai, mais je ne connais pas de fonction de sortie pour laquelle la spécification d'un format soit agréable; c'est, par nature, compliqué.

On trouve de nombreuses questions sur l'usage du %n dans les formats de sortie. Si nombreux sont ceux qui savent ce que cela fait, il est étrange de constater que tout aussi nombreux sont ceux qui n'en voient pas l'usage. Il est vrai qu'à l'ère des GUI  les affichages en mode texte paraissent désuets, et pourtant…

Voici ce qu'en raconte les manuels:

%n : Print nothing, but write number of characters successfully written so far into an integer pointer parameter.

Donc ce n'est pas un paramètre de sortie mais une valeur calculée lors d'une sortie et qui est rangée dans un entier dont l'adresse est spécifiée en argument.

Le code suivant :

int l;
printf("Bonjour%n\n",&l);
printf("%d\n",l);

génère à l'exécution la sortie suivante :

Bonjour
7

La valeur 7 correspond au nombre de caractères écrits depuis le début du premier printf jusqu'au %n, soit 7. Bien entendu, il s'agit de tout ce qui a été écrit, et non de la chaîne du format elle-même, ainsi :

int l;
char *s = "Au revoir";
printf("%s%n\n",s&,l);
printf("%d\n",l);

génère la sortie :

Au revoir
9

Alors à quoi cela sert-il ? À formater on vous dit! N'oublions pas que le formatage comporte deux aspects :

  • le premier est la représentation d'une valeur d'un type donné (entier en hexa, ou octal par exemple),
  • le second est le placement de la représentation dans l'espace réservé à l'affichage de la valeur du type (calage à gauche, bourrage, etc.).


%n sert pour le formatage d'un troisième genre : la vue tabulaire, c'est-à-dire le placement relatif et contrôlé des différentes représentations.

Si je souhaite afficher un tableau, comment m'en sortir ? La première solution est de parier sur la fixité de certains paramètres du tableau, comme dans ce qui suit :

printf("Table | i+100 | i+200 |\n");
for (int i=0; i<10; i++) {
printf("i=%-3d | %5d | %5d |\n",i,i+100,i+200);
}
return 0;

et qui permet d'obtenir :

Table | i+100 | i+200 |
i=0   |   100 |   200 |
i=1   |   101 |   201 |
i=2   |   102 |   202 |
i=3   |   103 |   203 |
i=4   |   104 |   204 |
i=5   |   105 |   205 |
i=6   |   106 |   206 |
i=7   |   107 |   207 |
i=8   |   108 |   208 |
i=9   |   109 |   209 |

parfait, mais cet affichage ne fonctionne que parce que les tailles des différentes colonnes ont été calculées à l'avance! Que faire si l'entête du tableau est obtenu via des valeurs calculées ou obtenues depuis l'extérieur ?

char *f1 = "Table", *f2="i+100", *f3="i+200";
int l1, l2, l3;
l1 = strlen(f1);
l2 = strlen(f2);
l3 = strlen(f2);
printf("%s | %s | %s |\n",f1,f2,f3);
for (int i=0; i<10; i++) {
        printf("i=%*d | %*d | %*d |\n",
               -(l1-2),i,l2,i+100,l3,i+200);
}

On remarquera au passage : l'usage de l'étoile * qui permet en sortie de spécifier via une variable la largeur du champ, et l'usage d'une valeur négative pour la longueur du champ qui signifie un calage à gauche...

Reste que l'on doit encore écrire des bouts de code pour calculer les longueurs. C'est là que %n débarque :

printf("%s%n | %s%n | %s%n\n",f1,&c,f2,&c1,f3,&c2);
for (int i=0; i<10; i++) {
        printf("i=%*d | %*d | %*d\n",
               -(c-2),i,c1-c-3,i+100,c2-c1-3,i+200);
}

Je vous laisse tester avec différentes valeurs pour les champs d'entête de colonne (f1, f2, et f3), voici ce que j'ai obtenu :

Ma table magique | Premiere colonne | Seconde
i=0              |              100 |     200
i=1              |              101 |     201
i=2              |              102 |     202
i=3              |              103 |     203
i=4              |              104 |     204
i=5              |              105 |     205
i=6              |              106 |     206
i=7              |              107 |     207
i=8              |              108 |     208
i=9              |              109 |     209
DjiBee

4 commentaires:

  1. Ben. Pour le coup, merci ! Ça existe depuis les origines du langage, ou ça a été rajouté à posteriori ?

    RépondreSupprimer
  2. J'ai pas eu le courage de chercher depuis quand c'est implanté. Je suis presque sûr que ce n'est pas à l'origine; mais je pense que c'est assez ancien.

    RépondreSupprimer
    Réponses
    1. Effectivement, on en trouve la définition dans le C89. Merci.

      Supprimer
  3. Ce commentaire a été supprimé par un administrateur du blog.

    RépondreSupprimer