Partie 2 : Les languages comme C/C++ ou Zig permettent de contrôler la mémoire mais à quel prix ?
Table des matières
Avec C, il n'y a pas de runtime, pas de GC.
char* msg = malloc(100);
strcpy(msg, "hello");
free(msg);
printf("%s", msg); // ❌ Use after free
Pièges Courants
| Problème | Code | Risque |
|---|---|---|
| Use-after-free | printf("%s", msg); |
Undefined behavior |
| Double free | free(msg); free(msg); |
Heap corruption |
| Buffer overflow | char buf[4]; strcpy(buf, "long"); |
Memory corruption |
| Memory leak | malloc(...) sans free |
Crashes lents |
Modèle de Mémoire Manuelle
Tu dois :
- Allouer la mémoire
- Tracker l'ownership
- La libérer manuellement
- Éviter d'accéder à la mémoire freed ou invalide
Conséquences Réelles
Voici quelques bugs connus.
Heartbleed (OpenSSL)
// Version simplifiée du bug
char* buffer = malloc(payload_length);
memcpy(buffer, payload, payload_length); // Pas de bounds check !
// L'attaquant pouvait lire au-delà du buffer
Impact : Plus de 500 000 serveurs ont exposé leurs clés privées et mots de passe.
CVE-2021-44228 (Équivalent Log4Shell en C)
char* user_input = get_user_data();
sprintf(log_buffer, "User: %s", user_input); // Buffer overflow possible
Le Problème : Pas de bounds checking automatique signifie que les attaquants peuvent :
- Crasher ton programme
- Exécuter du code arbitraire
- Voler des données sensibles
Sécurité de la mémoire niveau statique (dans le code statique, avant l'exécution / runtime)
Vulnérabilités de sécurité par catégorie :
- 70% des bugs sécurité Microsoft : problèmes de memory safety
- 65% des vulnérabilités Chrome : memory corruption
- ~50% des patches sécurité Android : liés à la mémoire
Un poid pour le développeur
Chaque allocation nécessite un tracking
typedef struct {
char* data;
size_t size;
} Buffer;
Buffer* create_buffer(size_t size) {
Buffer* buf = malloc(sizeof(Buffer));
if (!buf) return NULL;
buf->data = malloc(size);
if (!buf->data) {
free(buf); // Il faut se rappeler de nettoyer !
return NULL;
}
buf->size = size;
return buf;
}
void destroy_buffer(Buffer* buf) {
if (buf) {
free(buf->data); // Il faut free dans le bon ordre
free(buf);
}
}
Saturation mental : Chaque fonction doit considérer :
- Qui possède ce pointer ?
- Quand doit-il être liberé ?
- Est-il encore valide ?
Debugging des Problèmes Mémoire
$ valgrind ./my_program
==12345== Invalid read of size 4
==12345== at 0x40084B: main (test.c:10)
==12345== Address 0x5204044 is 0 bytes after a block of size 4 alloc'd
==12345== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
Un des problèmes majeurs : Les bugs sont découverts trop tard, au runtime, pas au moment de la compilation.
Trade-off Performance vs Sécurité
Caractéristiques Performance de C
// Zero overhead - accès mémoire direct
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += array[i]; // Pas de bounds checking
}
Vitesse : ✅ Performance maximale
Sécurité : ❌ Une erreur = vulnérabilité de sécurité
Contrôle de l'empreinte mémoire
// Contrôle précis de l'emprunte mémoire
struct Point {
float x, y, z; // Exactement 12 bytes
} __attribute__((packed));
Point* points = malloc(1000 * sizeof(Point)); // Allocation prévisible
Contrôle : ✅ Contrôle complet de l'emprunte mémoire Risque : ❌ Gestion manuelle des lifetimes
Les Outils aident, mais ne suffisent pas
Static Analysis
// clang-static-analyzer peut attraper certains problèmes
char* ptr = malloc(10);
free(ptr);
*ptr = 'x'; // ⚠️ Warning: use after free
Runtime Detection
// AddressSanitizer (ASan) attrape les bugs au runtime
$ gcc -fsanitize=address program.c
$ ./a.out
=================================================================
==12345==ERROR: AddressSanitizer: heap-use-after-free
La Limitation
- Outils static : Ratent les cas complexes, faux positifs
- Outils runtime : Ne détectent les bugs qui s'exécutent que pendant les tests
- Code review : Erreur humaine, chronophage
Pourquoi C est toujours utilisé malgré les risques
Exigences de programmation système
- Systèmes d'exploitation : Besoin d'accès direct au hardware
- Systèmes embarqués : Contraintes mémoire, pas de place pour un runtime
- Code critique en performance : Chaque nanoseconde compte
Legacy et Écosystème
- Bases de code massives : Décennies de code C en production
- Écosystème de librairies : La plupart des librairies système écrites en C
- Connaissance développeur : Générations de programmeurs C
Le Problème Fondamental
C te donne deux mauvais choix :
Option 1 : Gestion manuelle de la mémoire
char* data = malloc(size);
// ... logique complexe ...
if (error) {
free(data); // Il faut se souvenir du cleanup dans TOUS les chemins
return -1;
}
// ... plus de logique ...
free(data); // Facile d'oublier ou de double-free
Option 2 : Garbage collector
- Ajouter une librairie GC comme Boehm GC
- Perdre la prévisibilité des performances
- Toujours possible d'avoir des fuites mémoires
Points Clés
✅ Performance prévisible - pas de pauses GC
✅ Contrôle complet de l'emprunte mémoire
✅ Overhead runtime minimal
❌ Unsafe par défaut - une erreur = vulnérabilité
❌ Responsabilité élevé pour les développeurs
❌ La plupart des bugs sécurité viennent des problèmes mémoire
❌ Les outils détectent les bugs après qu'ils soient écrits, pas avant
Le Défi : Nous voulons la performance de C/C++ sans ses inconvénients.
La Question : Et si le compilateur pouvait prévenir les bugs mémoire au moment de la compilation ?
➡️ Suivant : "Voir la partie 3 de cette article"