Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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). Le NUL final 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 :

TypeCréé par (+1)Libéré par
AirLogair_log_openair_log_close
AirLogFieldsair_log_fields_newair_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_OK seul. Sur erreur, *out reste inchangé : il n’y a rien à libérer (n’appelez pas close/free sur un handle non obtenu).
  • close/free consomment 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).
  • NULL est un no-op pour air_log_close/air_log_fields_free : libérer un NULL est sûr (pratique pour les chemins d’erreur).
  • Le handle passé à air_log_emit (const AirLog *, const AirLogFields *) est emprunté (+0) : emit ne le libère pas et n’en garde pas de référence. C’est toujours à vous d’appeler close/free.

Tableau récapitulatif par symbole

SymboleRetourArgument(s)Fonction de libération
air_uuid_new_v7 / _v4AirStatusout emprunté +0 (POD, écrit sur OK)
air_uuid_to_hyphenatedAirStatusuuid +0 ; buf appelant +0
air_uuid_parseAirStatuss +0 (C-string) ; out +0
air_id128_machine_idAirStatusout +0 (POD)
air_id128_to_hexAirStatusid +0 ; buf appelant +0
air_monotonic_id_nextAirStatusout +0 (uint64_t)
air_status_messageconst char * statiquestatus (par valeur)ne pas libérer
air_log_openAirStatusnamespace_ +0 ; *out possédé +1air_log_close
air_log_closevoidconsomme log (NULL = no-op)(c’est la libération)
air_log_emitAirStatuslog / message / fields empruntés +0
air_log_lost_countAirStatuslog +0 ; out +0
air_log_fields_newAirStatus*out possédé +1air_log_fields_free
air_log_fields_freevoidconsomme fields (NULL = no-op)(c’est la libération)
air_log_fields_addAirStatusfields emprunté-mut +0 ; key/value +0
air_log_fields_add_bytesAirStatusfields +0 ; key +0 ; value +0 (len octets)
air_instant_nowAirStatusout +0 (POD)
air_instant_elapsedAirStatussince +0 ; out +0
air_instant_duration_sinceAirStatuslater/earlier +0 ; out +0
air_duration_from_secs/millis/nanosAirStatusout +0 (POD)
air_duration_as_secs_f64AirStatusd +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 C NUL-terminées attendues en UTF-8 ; un contenu non-UTF-8 rend AIR_STATUS_INVALID_DATA (jamais de déréférencement d’un NULL : passer NULL rend AIR_STATUS_NULL_ARGUMENT).
  • air_log_fields_add_bytes accepte une valeur binaire (len octets, sauts de ligne et NUL autorisé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.