Un langage qui ne cache rien
Ce que ce chapitre apporte5 points
- Écrire, exécuter et lire un programme C minimal.
- Nommer les trois choses que tout programme C possède : un point d'entrée, des types, une valeur de retour.
- Distinguer un langage compilé d'un langage interprété, et dire ce que chacun décide.
- Afficher une valeur avec printf et choisir la conversion qui correspond au type.
- Lire un message d'erreur et le relier à la ligne fautive.
Python cache la mémoire, et c'est ce qui le rend agréable. Une liste grandit toute seule, un entier n'a pas de taille, une variable oubliée finit par disparaître sans qu'on s'en occupe. Ce confort a un prix : rien de ce qui se passe réellement dans la machine n'est visible, et le jour où un programme est trop lent, trop gros, ou destiné à une carte sans système d'exploitation, ce voile devient un obstacle.
Le C ne cache rien. Chaque variable occupe un nombre d'octets qu'on peut afficher, chaque tableau est une adresse et une taille qu'il faut retenir soi-même, et la mémoire prise doit être rendue à la main. Ce parcours prend ce langage pour ce qu'il est : non pas un Python moins commode, mais l'outil qui rend la machine lisible.
Le plus petit programme qui tourne
Quatre choses s'y trouvent, et aucune n'est décorative.
La ligne #include <stdio.h> demande au préprocesseur d'insérer les déclarations des fonctions
d'entrée et de sortie, printf en tête. Sans elle, le compilateur ne saurait pas ce que printf
attend.
La fonction main est le point d'entrée : l'exécution commence là, toujours, et nulle part
ailleurs. Son nom n'est pas un choix, c'est une convention que le système attend.
Le int devant main annonce que la fonction rend un entier, et le return 0 le rend. Cette
valeur est le code de retour du programme : zéro signifie que tout s'est bien passé, et c'est
elle que le système d'exploitation examine.
Le void entre parenthèses dit que main ne prend aucun paramètre.
\n à la fin et relancer : la sortie ne va plus à la ligne, ce qui se voit dès qu'on affiche deux fois.Retirer enfin le point-virgule après le
printf : l'interpréteur refuse le programme et dit ce qu'il attendait. Un point-virgule manquant est la première erreur de tout débutant en C, et le message qu'elle produit vaut la peine d'être vu une fois volontairement.
Compilé, et ce que cela change
Un programme Python est lu ligne par ligne par un interpréteur, pendant l'exécution. Un programme C est traduit entièrement en instructions machine avant de s'exécuter, par un compilateur.
| Interprété | Compilé | |
|---|---|---|
| Quand les erreurs de syntaxe apparaissent | à l'exécution de la ligne | avant toute exécution |
| Ce qui est livré | le source | un exécutable, illisible |
| Vitesse d'exécution | plus lente | proche du maximum de la machine |
| Portabilité | le même fichier partout | un exécutable par machine cible |
C'est ce qui rend le C exigeant à l'écriture et sûr à l'exécution, exactement à l'inverse d'un langage interprété où une faute dort tranquillement dans une branche jamais empruntée.
Il rend même un service qu'aucun compilateur ne rend : il vérifie chaque accès mémoire et signale une lecture hors d'un tableau à l'endroit exact. Un vrai programme C ne dirait rien, lirait une valeur quelconque, et planterait plus tard ailleurs. C'est commode pour apprendre, et il faut savoir que la vraie machine, elle, ne prévient pas.
Afficher, et choisir la bonne conversion
printf prend un format, c'est-à-dire un texte où certains signes commençant par % seront
remplacés par les valeurs qui suivent.
| Conversion | Pour quel type |
|---|---|
%d | un entier |
%c | un caractère |
%f | un flottant, %.2f pour deux décimales |
%s | une chaîne |
%p | une adresse |
printf("%d", 3.5) ne fait pas planter un vrai programme C : il affiche un nombre quelconque, pris en lisant les octets d'un flottant comme s'ils étaient un entier. Le programme continue, et le chiffre faux se promène dans un rapport.C'est le premier exemple de ce que ce langage exige : le compilateur fait confiance au format, et personne ne vérifie qu'il correspond aux arguments.
%.2f par %d et relancer : la taille s'affiche comme 1, sans le moindre avertissement. L'interpréteur de ces pages tronque, une vraie machine afficherait un nombre sans rapport, et ni l'un ni l'autre ne signale quoi que ce soit.Ajouter ensuite une quatrième valeur à la dernière ligne sans ajouter de
% dans le format. Ce cas-là, l'interpréteur le refuse en disant combien d'arguments il a reçus et combien le format en consomme, parce que c'est une faute qu'il peut voir.
À calculer soi-même
Un programme C se raisonne en octets avant de se raisonner en valeurs.
Les tailles, dès le premier programme
- 1.
Combien d'octets un int occupe-t-il sur une machine ordinaire ?
- 2.
Combien d'octets un char occupe-t-il ?
- 3.
Combien d'octets un double occupe-t-il ?
- 4.
Combien de valeurs entières distinctes un int de 4 octets peut-il représenter ?
- 5.
Quelle est la plus grande valeur qu'un int SIGNÉ de 4 octets peut porter ?
- 6.
Un tableau de 100 int occupe combien d'octets ?
La dernière réponse est le genre de calcul qu'un programme Python ne demande jamais et qu'un programme C demande tout le temps. Réserver de la place suppose de savoir combien, et savoir combien suppose de connaître la taille des types.
Vérification
1.Par quelle fonction l'exécution d'un programme C commence-t-elle ?
2.Que vaut le return 0 à la fin de main ?
3.Que se passe-t-il si le format de printf ne correspond pas aux arguments ?
4.Quelle est la différence essentielle entre compiler et interpréter ?
Exercices type
Pourquoi faut-il écrire #include <stdio.h> pour employer printf ?
Parce que le compilateur doit connaître la signature de printf avant de voir un appel : combien
d'arguments elle prend, de quels types, et ce qu'elle rend. Le fichier stdio.h contient cette
déclaration.
Sans elle, le compilateur devine, et ses suppositions sont souvent fausses. C'est la différence entre un langage où tout est connu d'avance et un langage qui découvre les fonctions en chemin.
Que se passe-t-il si main ne contient pas de return ?
Depuis la norme de 1999, main rend implicitement zéro dans ce cas, et c'est la seule fonction du
langage à bénéficier de cette exception.
Pour toutes les autres fonctions déclarées avec un type de retour, l'absence de return donne une
valeur indéterminée, ce qui est une des sources de bogue les plus difficiles à voir : le programme
fonctionne, et rend parfois n'importe quoi.
Pourquoi le C demande-t-il de déclarer le type de chaque variable ?
Parce que le type décide de la place occupée et de l'interprétation des octets. Les mêmes quatre octets valent un entier, un flottant tronqué ou quatre caractères selon ce qu'on en dit.
Un langage qui gère la mémoire tout seul peut se permettre de retenir le type avec la valeur. Un langage qui ne cache rien laisse cette information à la charge du programme, et c'est ce qui permet de savoir, à l'avance, combien il occupera.
La méthode
- Commencer par le squelette : les inclusions,
int main(void),return 0. Il ne change jamais. - Déclarer chaque variable avec son type, au plus près de son premier usage.
- Choisir la conversion de
printfd'après le type, et non d'après ce qu'on espère afficher. - Relire le message d'erreur en entier, et commencer par la ligne qu'il nomme. Une erreur de compilation en entraîne souvent d'autres : seule la première est fiable.
- Exécuter tôt et souvent, plutôt que d'écrire cinquante lignes avant le premier essai.
Synthèse
- Tout programme C commence par
main, et rend un code de retour que le système examine. #includeapporte les déclarations dont le compilateur a besoin, avant tout appel.- Un langage compilé traduit tout avant d'exécuter : les erreurs de syntaxe apparaissent avant l'exécution, et ce qui est livré est un exécutable.
printfprend un format dont les conversions doivent correspondre aux types. Personne ne vérifie cette correspondance, et une erreur y est silencieuse.- Chaque type occupe un nombre d'octets connu : 1 pour un
char, 4 pour unint, 8 pour undouble. C'est ce qui permet de savoir ce qu'un programme occupera.
Et ensuite
Les tailles viennent d'être annoncées ; le chapitre suivant les met à l'épreuve, avec ce qu'elles impliquent de débordements et de conversions silencieuses. Les types et leur taille montre ce qui arrive quand un entier ne tient plus dans ses quatre octets.
Mettre en pratique
Le point d'entrée, le code de retour, la taille des types, et le format de printf que personne ne vérifie.
- Compter les octets avant d'écrireNiveau 1
- Le format et ses argumentsNiveau 2
- Le code de retour et ce qu'il permetNiveau 2