Langage C : types, pointeurs et mémoire
L'essentiel du C en MP2I : représentation des nombres, tableaux et chaînes, pointeurs, pile et tas, allocation dynamique et comportements indéfinis.
Le C est le langage « proche de la machine » du programme MP2I : il impose de comprendre comment les données sont représentées et où elles vivent en mémoire. Cette fiche rassemble ce qu'il faut maîtriser pour écrire des programmes corrects et répondre aux questions de cours des concours.
Types de base et représentation des nombres
| Type | Taille usuelle (PC 64 bits) | Contenu |
|---|---|---|
char | 1 octet (garanti) | caractère ou petit entier |
int | 4 octets | entier signé, à |
long | 8 octets (Linux) / 4 (Windows) | entier signé |
unsigned int | 4 octets | 0 à |
float | 4 octets | flottant IEEE 754 simple précision |
double | 8 octets | flottant IEEE 754 double précision |
Les flottants IEEE 754 stockent un signe, un exposant et une mantisse binaire : double = 1 + 11 + 52 bits, précision relative . La plupart des décimaux (0,1 ; 0,2) ne sont pas représentables exactement.
Piège : 7 / 2 vaut 3 (division entière tronquée vers zéro). Pour un quotient réel, convertir avant de diviser : (double) a / b. Ne jamais tester l'égalité de deux flottants avec == ; comparer fabs(x - y) < eps.
Les booléens sont des entiers : 0 est faux, tout le reste est vrai. Les opérateurs && et || sont évalués paresseusement (l'opérande droit n'est évalué que si nécessaire), ce qui autorise p != NULL && p->val > 0.
Tableaux et chaînes de caractères
Un tableau statique int t[10]; est un bloc contigu de 10 entiers ; les indices valides vont de 0 à 9 et aucun contrôle n'est fait à l'exécution. sizeof(t) vaut 10 * sizeof(int) tant que t est un tableau dans la portée courante.
Passé à une fonction, un tableau décroît en pointeur vers son premier élément : void f(int t[], int n) est strictement équivalent à void f(int *t, int n). La fonction ne connaît pas la taille, qu'il faut transmettre explicitement.
Une chaîne est un tableau de char terminé par le caractère nul '\0' : "abc" occupe 4 octets et strlen("abc") vaut 3. Un tableau prévu pour caractères doit avoir cases. Une chaîne littérale (char *s = "bonjour";) est en lecture seule : la modifier est un comportement indéfini ; char s[] = "bonjour"; crée une copie modifiable.
Pointeurs
Un pointeur est une variable qui contient une adresse. Avec int x = 3; int *p = &x; :
&xest l'adresse dex;pest la valeur stockée à l'adresse contenue dansp, c'est-à-direx;p = 10;modifiex.
L'arithmétique des pointeurs compte en éléments : si p est un int , p + 1 avance de sizeof(int) octets, et t[i] est défini comme (t + i). La taille d'un pointeur (8 octets en 64 bits) ne dépend pas du type pointé.
Le passage d'arguments se fait par valeur : une fonction reçoit des copies. Pour modifier une variable de l'appelant, on passe son adresse :
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
/* appel : swap(&x, &y); */
NULL est l'adresse conventionnelle « ne pointe vers rien » ; la déréférencer provoque en pratique une erreur de segmentation.
Méthode : pour lire une déclaration complexe, partir du nom et lire de droite à gauche : const int p est un pointeur vers un int constant (on peut changer p, pas p) ; int *const p est un pointeur constant vers un int modifiable.
Pile et tas : où vivent les données ?
| Zone | Qui alloue ? | Durée de vie | Usage |
|---|---|---|---|
| Pile (stack) | le compilateur, automatiquement | jusqu'à la sortie du bloc ou de la fonction | variables locales, paramètres, adresses de retour |
| Tas (heap) | le programmeur via malloc | jusqu'au free | données dont la taille ou la durée de vie dépend de l'exécution |
| Zone statique | le compilateur | tout le programme | globales, variables static, chaînes littérales |
Allocation dynamique
#include <stdlib.h>
int *t = malloc(n * sizeof(int)); /* n octets * sizeof, non initialisés */
if (t == NULL) { /* traiter l'échec */ }
/* ... utilisation de t[0] .. t[n-1] ... */
free(t);
t = NULL;
malloc(n)renvoieNULLen cas d'échec et n'initialise pas la mémoire ;calloc(n, taille)met tout à zéro.free(p)libère un bloc obtenu parmalloc;free(NULL)est sans effet.- Fuite mémoire : perdre l'adresse d'un bloc sans l'avoir libéré. Double
freeet utilisation aprèsfree: comportements indéfinis.
Piège : sizeof(t) / sizeof(t[0]) ne donne pas la longueur d'un tableau alloué par malloc (c'est un pointeur). Conserver la taille dans une variable, ou dans une structure.
Structures et types récursifs
typedef struct cell {
int val;
struct cell *next; /* l'alias 'cell' n'existe pas encore ici */
} cell;
cell *c = malloc(sizeof(cell));
c->val = 42; /* équivaut à (*c).val = 42 */
c->next = NULL;
L'opérateur -> accède à un champ via un pointeur. Les listes chaînées, piles et arbres du programme se construisent sur ce modèle : chaque maillon est alloué sur le tas et libéré explicitement.
Portée, durée de vie et compilation
Une variable déclarée dans un bloc n'est visible que dans ce bloc et masque toute variable homonyme extérieure. Une globale vit pendant tout le programme ; static dans une fonction conserve la valeur entre deux appels. Le préprocesseur traite #include et #define avant la compilation ; on compile toujours avec gcc -Wall -Wextra pour détecter les erreurs classiques (if (x = 0), variable non utilisée, conversion implicite douteuse).
Entrées-sorties : printf("%d %f %s\n", n, x, s) ; scanf("%d", &n) exige l'adresse ; fichiers avec FILE *f = fopen("nom", "r"), test f != NULL, lecture par fscanf ou fgets (qui renvoie NULL en fin de fichier), puis fclose(f).
Comportements indéfinis à connaître
- 1Accès hors bornes d'un tableau.
- 2Lecture d'une variable ou d'une case non initialisée.
- 3Déréférencement de
NULLou d'un pointeur pendant. - 4Débordement d'entier signé, division par zéro.
- 5
freedouble, utilisation aprèsfree, modification d'une chaîne littérale. - 6Expressions sans ordre d'évaluation défini comme
i = i++.
Méthodes de concours
- Toujours passer la taille avec un tableau et vérifier les bornes dans les boucles (
for (int i = 0; i < n; i++)). - Pour chaque
malloc: testerNULL, prévoir lefreecorrespondant, remettre le pointeur àNULLaprès libération. - Pour modifier une variable dans une fonction : passer son adresse. Pour une structure volumineuse : passer un pointeur plutôt qu'une copie.
- Dans une question « que affiche ce programme ? », suivre à la main les adresses et les valeurs, et repérer les divisions entières, les débordements et les chaînes sans
'\0'. - Savoir énoncer la différence pile/tas et la notion de comportement indéfini : ce sont des questions de cours fréquentes à l'oral.
Révise cette fiche avec les flashcards et le quiz du chapitre
Crée un compte gratuit pour retrouver cette fiche dans l'application, mémoriser les points clés avec les flashcards et te tester avec le quiz.
Réviser cette fiche gratuitementGratuit, sans carte bancaire.