Le langage C
Cours · informatique (MP2I/MPI), chapitre 1 · MP2I et MPI · le début du chapitre, la suite sur Adloun
1.1 Pourquoi commencer par C
Vous arrivez du lycée avec Python. Python vous a laissé écrire x = 3 puis x = "trois" sans broncher, il a fait grandir vos listes tout seul, il a récupéré la mémoire que vous n'utilisiez plus sans vous en parler. C ne fera rien de tout cela. C vous demandera de déclarer le type de chaque variable, de dire combien de cases vous voulez, et de rendre la mémoire que vous avez prise.
Ce n'est pas une brimade. Le programme officiel le dit : C est « un langage dit de bas niveau d'abstraction utilisé entre autres pour écrire tous les systèmes d'exploitation », et il « permet une gestion explicite de la mémoire et des ressources de la machine ». Autrement dit : ce que Python vous cachait, C vous l'apprend. Quand vous saurez pourquoi un tableau de dix cases n'accepte pas d'onzième valeur — et surtout, ce qui arrive quand on essaie quand même —, vous aurez compris quelque chose sur les machines, pas seulement sur un langage.
L'annexe A du B.O. « liste limitativement les éléments du langage C (norme C99 ou plus récente) dont la connaissance … est exigible ». Elle distingue deux niveaux, et ce chapitre les distingue aussi :
- ce qui doit être compris et utilisé sans rappel, y compris sans ordinateur — c'est le corps du chapitre ;
- ce qui doit être utilisable après rappel, documentation fournie — c'est la section sec:c-apres-rappel.
Le reste du langage C — et il y en a beaucoup — n'est pas au programme. On ne fait notamment pas d'arithmétique des pointeurs, et on n'utilise pas les opérateurs d'incrémentation ++ et --.
1.2 Un premier programme, et ce qu'il révèle
#include <stdio.h>
int main(void) {
printf("Bonjour\n");
return 0;
}
Quatre choses s'y lisent déjà, et aucune n'existait en Python :
#include <stdio.h>amène les déclarations d'entrées-sorties. Sans elle,printfest inconnu.int main(void): l'exécution commence toujours par la fonctionmain, et cette fonction a un type de retour,int.- Les accolades délimitent la portée. Les retours à la ligne et l'indentation, eux, ne sont pas signifiants : ils ne servent qu'à la lisibilité — mais ils y servent, et on les soigne.
return 0rend un code de sortie au système : zéro veut dire « tout s'est bien passé ».
1.2.1 Compilé, et non interprété
Python interprète : un programme lit votre texte et l'exécute au fil de la lecture. C compile : un programme lit votre texte en entier et fabrique un fichier exécutable, que la machine exécutera ensuite sans plus rien connaître de votre source.
bonjour.c --[compilation]--> bonjour.o --[édition de liens]--> bonjour
(source) (objet) (exécutable)
En pratique, une seule commande enchaîne les deux étapes :
gcc -Wall -Wextra -std=c99 -o bonjour bonjour.c
./bonjour
Bonne pratique (Toujours compiler avec les avertissements)
-Wall -Wextra demande au compilateur de signaler tout ce qui lui paraît douteux. Un programme C qui compile sans avertissement n'est pas forcément juste ; un programme qui en produit est presque toujours fautif. C'est le premier filet de sécurité, et il est gratuit.
Le programme demande de distinguer les deux. Un fichier .h (l'en-tête) déclare ce qu'une fonction fait — son nom, ses paramètres, son type de retour ; un fichier .c définit comment elle le fait. On inclut le premier là où l'on veut appeler la fonction ; on compile le second une fois. C'est la modularité de C, et c'est ce qui permet d'utiliser printf sans jamais en avoir lu le code.
1.3 Types de base, et la vérité sur les entiers
1.3.1 Les entiers ont une taille, et elle déborde
C distingue les entiers signés et non signés, et fixe leur taille en bits. L'annexe retient :
| Signés | Non signés | Bits |
|---|---|---|
int8_t | uint8_t | 8 |
int32_t | uint32_t | 32 |
int64_t | uint64_t | 64 |
Quand la taille exacte n'apporte rien, on écrit simplement int et unsigned int. Les opérations sont +, -, *, /, et % entre opérandes positifs.
Un int8_t vit entre et . Que vaut ?
int8_t x = 127;
x = x + 1; /* x vaut maintenant -128, sans le moindre message */
Python vous aurait donné : ses entiers sont de taille illimitée. C, lui, vous donne un nombre faux en silence. C'est la différence la plus dangereuse entre les deux langages, et c'est aussi pourquoi le programme insiste sur la programmation défensive : c'est à vous de vérifier qu'une somme ne débordera pas, personne ne le fera à votre place.
int a = 7 / 2; /* a vaut 3 : la division de deux entiers est entière */
double b = 7.0 / 2; /* b vaut 3.5 : dès qu'un opérande est flottant, tout l'est */
int c = 7 % 2; /* c vaut 1 : le reste */
La première ligne est le piège classique : rien n'indique que l'on a perdu la moitié.