C : Référence idiomatique et efficace
Table des matières
- Gestion mémoire
- Pointeurs et tableaux
- Gestion d'erreurs
- Chaînes de caractères
- Structures et énumérations
- Préprocesseur et en-têtes
- Anti-patrons spécifiques au C
1. Gestion mémoire {#memoire}
// ❌ malloc sans vérifier la valeur de retour
char *buf = malloc(size);
strcpy(buf, src);
// ✅
char *buf = malloc(size);
if (!buf) return -ENOMEM;
memcpy(buf, src, size);
// ❌ Cast du résultat de malloc (inutile en C, cache l'inclusion manquante)
int *p = (int *)malloc(n * sizeof(int));
// ✅
int *p = malloc(n * sizeof *p);
// ❌ free sans mettre à NULL (risque de pointeur pendant dans une portée longue)
free(ptr);
// ... du code ultérieur pourrait utiliser ptr
// ✅
free(ptr);
ptr = NULL;
// ❌ Oubli de libérer sur les chemins de retour anticipé
char *a = malloc(100);
char *b = malloc(200);
if (!b) return -1; // fuite de a
// ✅ — une seule étiquette de nettoyage
char *a = NULL, *b = NULL;
a = malloc(100);
if (!a) goto nettoyage;
b = malloc(200);
if (!b) goto nettoyage;
// ... utiliser a, b ...
nettoyage :
free(b);
free(a);
Utilisez sizeof *ptr au lieu de sizeof(Type) — cela reste correct lorsque le type change.
2. Pointeurs et tableaux {#pointeurs}
// ❌ Suivi manuel de la taille du tableau
void traiter(int *arr, int len) { ... }
traiter(donnees, 10);
// ✅ — passer la taille avec le pointeur, ou utiliser une structure
typedef struct { int *donnees; size_t len; } TrancheInt;
// ❌ Arithmétique de pointeurs alors que l'indiçage de tableau est plus clair
*(arr + i) = valeur;
// ✅
arr[i] = valeur;
// ❌ VLA dans du code de production (risque de débordement de pile, optionnel en C11+)
int arr[n];
// ✅
int *arr = malloc(n * sizeof *arr);
if (!arr) return -ENOMEM;
// ... utiliser arr ...
free(arr);
3. Gestion d'erreurs {#erreurs}
// ❌ Utilisation de nombres magiques pour les retours d'erreur
if (faire_quoi() == -1) { ... }
// ✅ — définir ou utiliser des codes d'erreur nommés
#include <errno.h>
if (faire_quoi() < 0) {
perror("faire_quoi");
return errno;
}
// ❌ Vérifications d'erreur profondément imbriquées
int r1 = etape1();
if (r1 == 0) {
int r2 = etape2();
if (r2 == 0) {
int r3 = etape3();
// ...
}
}
// ✅ — retour anticipé / goto nettoyage
if (etape1() < 0) goto echec;
if (etape2() < 0) goto echec;
if (etape3() < 0) goto echec;
return 0;
echec :
nettoyer();
return -1;
goto nettoyage est un idiome C pour la libération de ressources — ne l'évitez pas par principe.
4. Chaînes de caractères {#chaines}
// ❌ strcpy sans vérification de bornes
strcpy(dest, src);
// ✅
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
// ou mieux : snprintf(dest, sizeof(dest), "%s", src);
// ❌ Mauvaise utilisation de strcmp
if (str == "bonjour") { ... } // compare les pointeurs, pas le contenu
// ✅
if (strcmp(str, "bonjour") == 0) { ... }
// ❌ Construction de chaînes avec strcat répété (O(n²))
char resultat[1024] = "";
for (int i = 0; i < n; i++) {
strcat(resultat, elements[i]);
}
// ✅ — suivre la position d'écriture
char resultat[1024];
int pos = 0;
for (int i = 0; i < n && pos < (int)sizeof(resultat); i++) {
pos += snprintf(resultat + pos, sizeof(resultat) - pos, "%s", elements[i]);
}
Préférez snprintf à sprintf — toujours.
5. Structures et énumérations {#structures}
// ❌ Structure nue nécessitant le mot-clé `struct` partout
struct point { int x, y; };
struct point p = {1, 2};
// ✅
typedef struct { int x, y; } Point;
Point p = {1, 2};
// ❌ Structure non initialisée
Point p;
utiliser(p.x); // comportement indéfini
// ✅
Point p = {0};
// ❐ Constantes entières magiques
if (etat == 3) { ... }
// ✅
typedef enum { ETAT_INACTIF, ETAT_EN_COURS, ETAT_TERMINE } Etat;
if (etat == ETAT_TERMINE) { ... }
6. Préprocesseur et en-têtes {#preprocesseur}
// ❌ Macro alors qu'une fonction inline fonctionne (pas de sécurité de type, double évaluation)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
MAX(x++, y) // x incrémenté deux fois si x > y
// ✅
static inline int max_int(int a, int b) { return a > b ? a : b; }
// ❌ Pas de garde d'inclusion
// mon_en_tete.h
struct Foo { int x; };
// ✅
#ifndef MON_EN_TETE_H
#define MON_EN_TETE_H
struct Foo { int x; };
#endif
// ou : #pragma once (largement supporté, pas standard)
Réservez les macros pour la compilation conditionnelle et les constantes. Utilisez static inline pour la logique.
7. Anti-patrons spécifiques au C {#antipatrons}
| Anti-patron | Préféré |
|---|---|
sprintf | snprintf avec taille de tampon |
gets | fgets (gets supprimé en C11) |
Caster le résultat de malloc | laisser la conversion implicite void* fonctionner |
sizeof(Type) dans malloc | sizeof *ptr |
| VLA pour les grands tableaux/dynamiques | allocation sur le tas |
Callbacks void* sans paramètre de contexte | passer void *ctx avec le pointeur de fonction |
| État mutable global | passer l'état via des pointeurs de structure |
assert pour la gestion des erreurs à l'exécution | codes de retour d'erreur appropriés |
const manquant sur les paramètres pointeur en lecture seule | const char *str |
| Mélange signé/non signé dans les comparaisons | utiliser des types cohérents, caster explicitement |
Limites
- Ce sont des directives spécifiques au langage et ne couvrent pas les décisions architecturales globales.
- Une trop grande compression peut réduire la lisibilité ; faites preuve de discernement.