Situer chaque donnée dans sa zone, déjouer les macros, trouver fuites et débordements, puis écrire avec malloc - concaténation, matrices, et un pool de recyclage. Chapitre 3.
On se place sur un PC 64 bits sous Linux. Les programmes se compilent avec gcc -std=c11 -Wall -Wextra -Werror, et l'on dispose de valgrind.
Exercice · Où vit chaque donnée ?
Pour chacune des données de ce programme, dire dans quelle zone elle vit (statique, pile, tas, ou lecture seule) et combien de temps elle vit. Qu'affiche le programme ?
#include <stdio.h>
#include <stdlib.h>
int compteur;
const char *nom = "zones" ;
int *suivant(void )
{
static int n = 0 ;
int *p = malloc(sizeof *p);
if (p != NULL )
*p = ++n;
compteur++;
return p;
}
int main(void )
{
int *a = suivant();
int *b = suivant();
if (a == NULL || b == NULL )
return 1 ;
printf("%s : %d %d %d\n" , nom, *a, *b, compteur);
free(a);
free(b);
return 0 ;
}
Les données : compteur, nom, la chaîne "zones", n, p, l'entier pointé par p, a, b.
Exercice · Macros
Qu'affiche ce programme ? Corriger les macros pour qu'il fasse ce qu'on attend.
#include <stdio.h>
#define DOUBLE(x) x + x
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main(void )
{
int i = 5 , j = 3 ;
int m;
printf("%d\n" , 3 * DOUBLE(2 ));
m = MAX(i++, j);
printf("m = %d, i = %d\n" , m, i);
return 0 ;
}
Exercice · Une concaténation
On veut une fonction qui rend la concaténation de deux chaînes.
#include <stdio.h>
#include <string.h>
char *concat(const char *a, const char *b)
{
char buf[100 ];
strcpy(buf, a);
strcat(buf, b);
return buf;
}
int main(void )
{
printf("%s\n" , concat("bon" , "jour" ));
return 0 ;
}
Trouver les deux problèmes de cette fonction. Que dit gcc -Wall ? La réécrire correctement. Qui doit libérer le résultat ? Le dire dans un commentaire.
Exercice · Ce que voit valgrind
Ce programme compile sans avertissement et affiche 0 puis 14, ce qui est juste.
#include <stdio.h>
#include <stdlib.h>
int *carres(size_t n)
{
int *t = malloc(n * sizeof *t);
for (size_t i = 0 ; i < n; i++)
t[i] = i * i;
return t;
}
int somme_carres(size_t n)
{
int *t = carres(n);
int s = 0 ;
if (n == 0 )
return 0 ;
for (size_t i = 0 ; i <= n; i++)
s += t[i];
free(t);
return s;
}
int main(void )
{
printf("%d\n" , somme_carres(0 ));
printf("%d\n" , somme_carres(4 ));
return 0 ;
}
Trouver les trois erreurs, sans l'exécuter. Pourquoi le programme affiche-t-il quand même le bon résultat ? Corriger.
Exercice · Matrices sur le tas
On veut une matrice d'entiers de h lignes et w colonnes, dont les dimensions ne sont connues qu'à l'exécution, et qu'on puisse écrire m[y][x].
Écrire int **matrice_nouvelle(size_t h, size_t w), qui réserve un tableau de h pointeurs, puis une ligne de w entiers nuls pour chacun. Si une allocation échoue, tout ce qui a déjà été réservé doit être libéré, et la fonction rend NULL. Écrire void matrice_liberer(int **m, size_t h). Comparer avec un seul bloc calloc(h * w, sizeof (int)), auquel on accède par m[y * w + x] : nombre d'appels à malloc, facilité de libération, place en mémoire.
Exercice · Un pool de recyclage
Une liste chaînée alloue et libère sans cesse des maillons, tous de la même taille :
struct noeud {
int valeur;
struct noeud *suivant;
};
On veut remplacer malloc et free par un pool de 1024 maillons, réservé d'un coup :
#define POOL_TAILLE 1024
struct pool {
struct noeud blocs[POOL_TAILLE];
struct noeud *libres;
};
libres est la liste des maillons libres, chaînée par leur propre champ suivant : un maillon libre n'a pas besoin de sa valeur, on réutilise sa place.
Écrire void pool_init(struct pool *p), qui chaîne les 1024 blocs dans la liste des libres. Écrire struct noeud *pool_allouer(struct pool *p) (rend NULL si le pool est épuisé) et void pool_liberer(struct pool *p, struct noeud *n). Quel est leur coût ? Dans main, faut-il déclarer le pool comme une variable locale ? Combien d'octets occupe-t-il ?