«  « Printf » or not « printf » ?  »

That is la question

Présentation…

On est souvent fière au premiers instants d’aborder le langage C. On se sent entrer dans la cours des grands ? C’est bien cela ? Mais en débutant avec la programmation en C, on est souvent loin de pouvoir admettre que c’est bien d’abord pour le pire avant d’être pour le meilleur… puisque ce n’est jamais pour le meilleure. La fonction « printf » est caractéristique du langage C, mais elle est tout aussi symbolique de la dégradation de la qualité des logiciels et c’est le mythe de cette fonction « printf » que nous allons démonter ici.

À quoi sert « printf » au juste ?

« Printf » est l’un de ces fonctions qui en fait tellement, qu’on ne sait plus vraiment ce qu’elle doit faire. Après tout, « réunissons toutes les interfaces d’une bibliothèque dans une seule fonction », telle semble bien être la devise de « printf » et de ceux ou celles qui l’ont conçus.

Si vous lisez cette page, c’est que vous pratiquez déjà le développement logiciel ( développement, pas de bricolage.. promis ? ), et que vous avez donc déjà conçus votre petite librairie de fonctions, que ce soit en QBasic, en Pascal ( félicitation ) ou en Ada ( encore plus de bravos ). Vous serait-il venu une seconde à l’esprit de réunir toutes vos fonctions au travers d’une seule ? Probablement pas, et cela, pour la bonne raison qu’elle ne partage pas toutes la même interface logique, ni les même paramètres.

« Printf » et le gaspillage organisé

À la contestation de cette fusion d’interfaces hétéroclites au sein d’une seule et même fonction, certain(e)s répondront que ceci augmente l’efficacité du code produit. Ceci n’est qu’illusoire, car les différentes fonctions sous-jacentes doivent bien exister en sous-couche, tant il est vrai que « printf » ne fait pas de la magie.

De plus, l’économie d’un certain nombre d’appels de fonctions est finalement en pure perte, puisque la technique de passage de paramètres rendue obligatoire par ce type de fonction, pénalise tout les appels de fonction, même ceux qui n’ont aucune relation avec « printf ». Vous lisez bien et vous ne rêvez pas. « Printf » nécessite une stratégie de passage de paramètre, qui a fait la définition de la stratégie de passage de paramètres en C. Et la stratégie de passage de paramètre du C prend plus de temps et de code binaire que la stratégie employé en pascal par exemple.

Ainsi, même les fonctions les plus simples en C, se voient obligées d’appliquer une convention qui ne fût inventée que pour permettre des bizarreries à la « printf », s’en trouvant ainsi pénalisées. Qu’à peine environ 5% seulement ( de manière imagée ) des fonctions utilisées en C requièrent véritablement cette convention de passage de paramètres, n’empêche pas tous les appels de fonctions d’en subir les conséquences.

« Printf » va même encore plus loin dans le gaspillage. En effet, puisque « printf » cache derrière elle, des fonctions aussi diverses que l’affichage de nombres en virgule flottante, l’alignement de texte, ou autre… la seule liaison à « printf » impose la liaison de l’application vers toutes ces fonctions réunies ( en effet, le lieur n’a aucun moyen de savoir pour quelles fonctions « printf » est concrètement appelée ).

Ainsi, un code aussi simple que celui-ci…

    printf ("Coucou tout le monde! :)\n");

        

… va suffire à faire lier votre code vers tout un ensemble de fonctions toutes aussi inutiles les unes que les autres à cette seule tâche, et même amener à lier par exemple avec des fonctions mathématiques.

Saisissez et compilez en statique, ce simple petit programme sous Linux.

    #include <stdio.h>

    int main (int argc, char* argv [])
    {
       printf ("Coucou tout le monde! :)\n");
       return 0;
    }


        

… même après l’avoir « stripé », vous constaterez que ce si simple petit programme pèse plus de 64 KB. Quand on pense qu’il existe des systèmes d’exploitations qui pèsent moins que ça… Sans compter que nombre d’applications soigneusement codées pèsent bien moins que cela ( mais le codage soigné n’est pas le point fort du C, pas plus que celui du monde de Linux ).

La question ne sera pas exposée ici, mais cette mauvaise habitude prise avec le développement tout en C a eu de fâcheuses conséquences sur la qualité des logiciels en général.

Sans « printf » maintenant, saisissez, et compiler là encore en statique, le source suivant…

    int main (int argc, char* argv [])
    {
       return 0;
    }

        

Cette fonction ne fait rien… le code produit est beaucoup plus léger qu’avec « printf ». Mais là encore, bien que cette fonction ne fasse rien et même après un « stripage », le fichier du programme pèse encore plus de plusieurs KB… il faut dire qu’avec l’esprit « printf », le développement dans le monde C et Linux n’a pas vraiment appris à économiser, et encore moins à optimiser.

Le dérive observé dans l’habitude de lier des quantités hallucinante de librairie pour implémenter des fonctions très simples, trouve son symbole le plus parlant dans la fameuse fonction « printf ».

« Printf » et le « profiling »

Une autre remarque vient à l’esprit, quand on pense au profiling. Le profiling est une tâche ardue et variée. Et quand il s’agit d’économiser, et d’alléger le poids et la consommation d’une application, la première chose à surveiller, ce sont ces dépendances ( par les librairies, statiques, mais aussi dynamiques ).

Mais que peut bien vous apprendre le fait qu’une application dépende de « printf » ? Rien, et rien. Une application dépendant de « printf » peut aussi bien dépendre de l’une quelconque parmi des centaines de fonctions de formatage de texte, ou dépendre d’une librairie mathématique ( qui pèse lourd, surtout sur les anciens systèmes ), dépendre d’une librairie de localisation, etc, etc.

« Printf » participe à la désorganisation du code… et en est un emblème, bien que n’en étant pas la seul cause. Cependant une culture qui a toléré « printf », n’en est sûrement pas à sa première erreur du genre, ni à sa dernière.

Les alternatives

Vous avez besoin d’afficher une simple ligne de texte ?

Pas besoin de….

    #include <stdio.h>

    int main (int argc, char* argv [])
    {
       char* message = "abcdef…";
       printf ("%s\n", message);
       return 0;
    }

        

… plutôt cela, qui suffira amplement…


    #include <stdio.h>

    int main (int argc, char* argv [])
    {
       char* message = "abcdef…\n";
       fputs (message, STDOUT);
       return 0;
    }

        

Si vous devez transformez des valeurs numériques en texte, sachez que l’on obtient le caractère correspondant à un digit décimal, avec une instruction aussi simple que celle-ci :

    c = ’0’ + d; // d was checked to be in range 0 .. 9


        

Si toujours vous devez transformez des valeurs numériques en texte, sachez que l’on obtient le caractère correspondant à un digit hexadécimal, avec une table aussi simple que la suivante :

    static const char* hexadecimal_digits = "0123456789ABCDEF";


        

Sans compter que la dépendance à des librairies peut dans bien des cas empêcher une application de fonctionner, même si les librairies en question ne seraient pas strictement nécessaires à la logique de l’application. Et chacun sait à quel point il est beaucoup plus difficile de faire fonctionner une application sous Linux que sous Windows, justement dut à la lourdeur des dépendances sous Linux ( à telle point que certaines applications conçus initialement pour Linux fonctionnent mieux sous Windows que sous Linux ).

Pourtant avec un peu de patience et de soin, vous pouvez permettre à vos applications d’économiser quelques dizaine de KB et d’économiser quelques dépendances étouffantes… ce qui est le respect minimum que tout(e) développeur(se) doit aux utilisateur(ice)s de ses applications.