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.
Dans cette page …
À 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.
La suite…
À propos d’une solution d’architecture légère : Créer un fichier ELF sans même GNU LD