Propriété mémoire & cycle de vie
LE point critique de l’ABI C. En C, rien ne vous rappelle de libérer — le compilateur ne suit pas la propriété. Cette page énonce, sans ambiguïté et pour chaque symbole, qui alloue, qui libère, et avec quelle fonction. Un développeur C doit toujours savoir s’il faut libérer un type, et comment.
La doctrine de propriété n’est pas un guide à part qui dériverait : elle naît du
commentaire /// de chaque symbole (reporté dans le header, rendu par Doxygen)
et est synthétisée ici (ADR-027 §B).
La règle uniforme : +1 au RETOUR, +0 en ARGUMENT
Une seule règle gouverne tout le modèle d’objet Air :
- Un type complexe RETOURNÉ est possédé par l’appelant (+1). Vous en êtes responsable → vous devez le libérer avec sa fonction de libération dédiée.
- Un type complexe passé en ARGUMENT est emprunté (+0). Vous gardez la
propriété ; l’appelé ne le libère pas et ne conserve aucune référence au-delà
de l’appel (sauf clause
///explicite — il n’y en a aucune en T1/T3).
Tout le reste découle de cette règle. Les trois catégories de types ci-dessous précisent comment elle s’applique concrètement.
Catégorie 1 — POD passés par valeur (rien à libérer)
AirUuid, AirId128, AirInstant, AirDuration, AirLogLevel, AirStatus sont
des POD (Plain Old Data, #[repr(C)]) trivialement copiables. Ils vivent là
où vous les déclarez (pile, struct, tableau) : aucune allocation Air, aucune
fonction de libération.
AirUuid u; /* sur la pile de l'appelant */
air_uuid_new_v7(&u); /* `&u` emprunté (+0), écrit sur OK */
/* `u` disparaît à la fin du scope — rien à faire. */
Quand un POD est passé par pointeur (const AirUuid *uuid, AirDuration *out),
c’est uniquement pour éviter une copie ou écrire une sortie : la mémoire reste la
vôtre (+0).
Catégorie 2 — Sorties chaînes par buffer appelant (rien à libérer)
Les fonctions de formatage (air_uuid_to_hyphenated, air_id128_to_hex) n’allouent
rien. Vous fournissez le buffer ; Air y écrit :
char buf[AIR_UUID_HYPHENATED_LEN]; /* 37, NUL inclus */
AirStatus st = air_uuid_to_hyphenated(&u, buf, sizeof buf);
/* `buf` est à vous (+0). Trop petit ⇒ AIR_STATUS_BUFFER_TOO_SMALL, rien écrit. */
- Dimensionnez avec les constantes du header, jamais un littéral :
AIR_UUID_HYPHENATED_LEN(37),AIR_ID128_HEX_LEN(33). LeNULfinal est inclus. - Buffer trop court ⇒
AIR_STATUS_BUFFER_TOO_SMALL, aucune écriture partielle (ADR-032). - Aucune
air_*_free: la mémoire est la vôtre, sa durée de vie est la vôtre.
Cas particulier — air_status_message renvoie un const char * vers une chaîne
statique : elle n’est jamais libérée, jamais NULL, valide pour toute la
durée du programme. Ne la libérez pas (ce serait une faute).
Catégorie 3 — Handles opaques (+1 au retour → vous DEVEZ libérer)
AirLog et AirLogFields sont des handles opaques : des typedef struct …
jamais déréférencés côté C. Ils sont alloués par Air et rendus possédés (+1) ;
chacun a une fonction de libération dédiée :
| Type | Créé par (+1) | Libéré par |
|---|---|---|
AirLog | air_log_open | air_log_close |
AirLogFields | air_log_fields_new | air_log_fields_free |
AirLog *log = NULL;
if (air_log_open(NULL, &log) == AIR_STATUS_OK) { /* +1 : log possédé */
/* … usage … */
air_log_close(log); /* libération obligatoire */
}
Règles de cycle de vie des handles :
- Écrits sur
AIR_STATUS_OKseul. Sur erreur,*outreste inchangé : il n’y a rien à libérer (n’appelez pasclose/freesur un handle non obtenu). close/freeconsomment le handle (+1 → libéré). Après l’appel, le pointeur est invalide : ne le réutilisez plus, et ne le libérez pas deux fois (double-free interdit).NULLest un no-op pourair_log_close/air_log_fields_free: libérer unNULLest sûr (pratique pour les chemins d’erreur).- Le handle passé à
air_log_emit(const AirLog *,const AirLogFields *) est emprunté (+0) :emitne le libère pas et n’en garde pas de référence. C’est toujours à vous d’appelerclose/free.
Tableau récapitulatif par symbole
| Symbole | Retour | Argument(s) | Fonction de libération |
|---|---|---|---|
air_uuid_new_v7 / _v4 | AirStatus | out emprunté +0 (POD, écrit sur OK) | — |
air_uuid_to_hyphenated | AirStatus | uuid +0 ; buf appelant +0 | — |
air_uuid_parse | AirStatus | s +0 (C-string) ; out +0 | — |
air_id128_machine_id | AirStatus | out +0 (POD) | — |
air_id128_to_hex | AirStatus | id +0 ; buf appelant +0 | — |
air_monotonic_id_next | AirStatus | out +0 (uint64_t) | — |
air_status_message | const char * statique | status (par valeur) | ne pas libérer |
air_log_open | AirStatus | namespace_ +0 ; *out possédé +1 | air_log_close |
air_log_close | void | consomme log (NULL = no-op) | (c’est la libération) |
air_log_emit | AirStatus | log / message / fields empruntés +0 | — |
air_log_lost_count | AirStatus | log +0 ; out +0 | — |
air_log_fields_new | AirStatus | *out possédé +1 | air_log_fields_free |
air_log_fields_free | void | consomme fields (NULL = no-op) | (c’est la libération) |
air_log_fields_add | AirStatus | fields emprunté-mut +0 ; key/value +0 | — |
air_log_fields_add_bytes | AirStatus | fields +0 ; key +0 ; value +0 (len octets) | — |
air_instant_now | AirStatus | out +0 (POD) | — |
air_instant_elapsed | AirStatus | since +0 ; out +0 | — |
air_instant_duration_since | AirStatus | later/earlier +0 ; out +0 | — |
air_duration_from_secs/millis/nanos | AirStatus | out +0 (POD) | — |
air_duration_as_secs_f64 | AirStatus | d +0 ; out +0 (double) | — |
Lecture du tableau : une ligne avec une fonction de libération non vide est la seule où vous avez quelque chose à libérer. Toutes les autres : rien.
Encodage des chaînes & buffers
- Les
const char *d’entrée sont des chaînes CNUL-terminées attendues en UTF-8 ; un contenu non-UTF-8 rendAIR_STATUS_INVALID_DATA(jamais de déréférencement d’unNULL: passerNULLrendAIR_STATUS_NULL_ARGUMENT). air_log_fields_add_bytesaccepte une valeur binaire (lenoctets, sauts de ligne etNULautorisés) : c’est le seul cas « octets bruts », emprunté +0 le temps de l’appel.- Les buffers de sortie sont toujours fournis et possédés par vous (catégorie 2).
Secrets & refcompté
En T1/T3, aucun type ne transporte de secret et aucun n’expose de
retain/release : la règle « +1 retour / +0 argument » suffit. Quand la libc Air
introduira des objets refcomptés ou porteurs de secrets, leur clause /// dédiée le
précisera explicitement (paire retain/release, effacement zeroize à la charge de
qui) — toujours selon le même principe : la propriété est gravée dans le commentaire
de chaque symbole.
English version: Memory ownership & lifecycle.