Section courante

A propos

Section administrative du site

 Langage  Elément  Tutoriel  Aide 
ABAP/4
Ada
Assembleur
Assembly & bytecode
ASP (Active Server Pages)
Basic
C
C++
C# (C Sharp)
Cobol
ColdFusion
Fortran
HTML
Java
JavaScript
LISP
Logo
LotusScript
Oberon
Pascal
Perl
PHP
PL/1
Prolog
Python
Rebol
REXX
Ruby
Rust
SAS
NoSQL
SQL
Swift
X++ (Axapta)
GNAT
SMALLAda
VHDL
Assembleur 370
Assembleur 1802
Assembleur 4004
Assembleur 6502
Assembleur 6800
Assembleur 68000
Assembleur 8080 et 8085
Assembleur 8089
Assembleur 80x86
Assembleur AGC4
Assembleur ARM
Assembleur DPS 8000
Assembleur i860
Assembleur Itanium
Assembleur MIPS
Assembleur PDP-11
Assembleur PowerPC
Assembleur RISC-V
Assembleur SPARC
Assembleur SuperH
Assembleur UNIVAC I
Assembleur VAX
Assembleur Z80
Assembleur Z8000
Assembleur z/Architecture
ASSEMBLER/MONITOR 64
Micol Assembler
GFA Assembler
A86
MASM (Macro Assembler)
TASM (Turbo Assembler)
CIL
Jasmin
LLVM
MSIL
Parrot
P-Code (PCode)
SWEET16
G-Pascal
ASP 1.0
ASP 2.0
ASP 3.0
ASP.NET
ASP.NET Core
ABasiC (Amiga)
Adam SmartBASIC
Altair BASIC
AmigaBASIC (Amiga)
AMOS Basic (Amiga)
Atari Basic (Atari 400, 600 XL, 800, 800XL)
Basic Apple II (Integer BASIC/APPLESOFT)
Basic Commodore 64 (CBM-BASIC)
Basic Commodore 128 (BASIC 7.0)
Basic Commodore VIC-20 (CBM-BASIC 2.0)
Basic Coco 1 (Color Basic)
Basic Coco 2 (Extended Color Basic)
Basic Coco 3 (Extended Color Basic 2.0)
BASICA (PC DOS)
Basic Pro
BBC BASIC
Blitz BASIC (Amiga)
DarkBASIC
Dartmouth BASIC
GFA-Basic (Atari ST/Amiga)
GWBASIC (MS-DOS)
Liberty BASIC
Locomotive BASIC (Amstrad CPC)
MSX-Basic
Omikron Basic (Atari ST)
Oric Extended Basic
Power Basic
Quick Basic/QBasic (MS-DOS)
Sinclair BASIC (ZX80, ZX81, ZX Spectrum)
ST BASIC (Atari ST)
Turbo Basic
Vintage BASIC
VBScript
Visual Basic (VB)
Visual Basic .NET (VB .NET)
Visual Basic pour DOS
Yabasic
BeckerBASIC
SIMONS' BASIC
Basic09 d'OS-9
Disk Extended Color Basic
Basic09 d'OS-9
Disk Extended Color Basic
Access
Excel
Visual Basic pour Windows
Visual Basic .NET pour Windows
C Shell Unix (csh)
C pour Amiga
C pour Atari ST
C pour DOS
C pour Falcon030
C pour GEMDOS (Atari ST)
C pour Linux
C pour PowerTV OS
C pour OS/2
C pour Unix
C pour Windows
Aztec C
CoCo-C
GNU C
HiSoft C
IBM C/2
Introl-C
Lattice C
Microsoft C
MinGW C
MSX-C
Open Watcom C
OS-9 C Compiler
Pure C
Quick C
Turbo C
HiSoft C for Atari ST
HiSoft C for CP/M (Amstrad CPC)
C++ pour OS/2
C++ pour Windows
Borland C++
C++Builder
IBM VisualAge C++
Intel C++
MinGW C++
Open Watcom C++
Symantec C++
Turbo C++
Visual C++
Visual C++ .NET
Watcom C++
Zortech C++
C# (C Sharp) pour Windows
Apple III Cobol
Microsoft Cobol
BlueDragon
Lucee
OpenBD
Railo
Smith Project
Microsoft Fortran
WATFOR-77
CSS
FBML
Open Graph
SVG
XML
XSL/XSLT
LESS
SASS
GCJ (GNU)
JSP
Jython
Visual J++
Node.js
TypeScript
AutoLISP
ACSLogo
LotusScript pour Windows
Amiga Oberon
Oberon .NET
Apple Pascal
Delphi/Kylix/Lazarus
Free Pascal
GNU Pascal
HighSpeed Pascal
IBM Personal Computer Pascal
Lisa Pascal
Maxon Pascal
MPW Pascal
OS-9 Pascal
OSS Personal Pascal
Pascal-86
Pascal du Cray Research
Pascal/VS
Pascal-XT
PURE Pascal
QuickPascal
RemObjets Chrome
Sun Pascal
THINK Pascal
Tiny Pascal (TRS-80)
Turbo Pascal
UCSD Pascal
VAX Pascal
Virtual Pascal
Turbo Pascal for CP/M-80
Turbo Pascal for DOS
Turbo Pascal for Macintosh
Turbo Pascal for Windows
CodeIgniter (Cadre d'application)
Drupal (Projet)
Joomla! (Projet)
Phalanger (PHP .NET)
phpBB (Projet)
Smarty (balise)
Twig (balise)
Symfony (Cadre d'application)
WordPress (Projet)
Zend (Cadre d'application)
PL360
PL/M-80
PL/M-86
Turbo Prolog
CPython
IronPython
Jython
PyPy
AREXX
Regina REXX
JMP
Btrieve
Cassandra
Clipper
CouchDB
dBASE
Hbase
Hypertable
MongoDB
Redis
Access
BigQuery
DB2
H2
Interbase
MySQL
Oracle
PostgreSQL
SAP HANA
SQL Server
Sybase
U-SQL
Introduction
Fichiers d'entêtes de la bibliothèque
Référence de fonctions
Les premiers pas
Les rapports d'erreurs
Allocation et pagination de la mémoire virtuelle
Préface
Notes légales
Dictionnaire
Recherche

Allocation et pagination de la mémoire virtuelle

Cette page décrit comment les processus gèrent et utilisent la mémoire dans un système utilisant la bibliothèque glibc.

La bibliothèque glibc possède plusieurs fonctions permettant d'allouer dynamiquement la mémoire virtuelle de diverses manières. Elles varient en généralité et en efficacité. La bibliothèque fournit également des fonctions permettant de contrôler la pagination et l'allocation de la mémoire réelle.

Les entrées/sorties cartographiées en mémoire ne sont pas abordées dans cette page. Voici les sujets abordés dans cette page :

Concepts de mémoire de processus

L'une des ressources les plus simples dont dispose un processus est la mémoire. Il existe de nombreuses façons différentes d'organiser la mémoire dans les systèmes, mais dans un système typique, chaque processus dispose d'un espace d'adressage virtuel linéaire, avec des adresses allant de zéro à un maximum énorme. Il n'est pas nécessaire qu'il soit contigu, c'est-à-dire que toutes ces adresses ne peuvent pas être utilisées pour entreposer des données.

La mémoire virtuelle est divisée en pages (4 kilo-octets en général). Chaque page de mémoire virtuelle est sauvegardée par une page de mémoire réelle (appelée trame) ou par un entreposage secondaire, généralement de l'espace disque. L'espace disque peut être un espace de swap ou simplement un fichier disque ordinaire. En fait, une page contenant uniquement des zéros n'a parfois rien du tout derrière elle - il y a juste un drapeau indiquant qu'elle ne contient que des zéros.

La même trame de mémoire réelle ou de stockage de sauvegarde peut sauvegarder plusieurs pages virtuelles appartenant à plusieurs processus. C'est normalement le cas, par exemple, avec la mémoire virtuelle occupée par le code de la bibliothèque glibc. La même trame de mémoire réelle contenant la fonction printf sauvegarde une page de mémoire virtuelle dans chacun des processus existants ayant un appel printf dans leur programme.

Pour qu'un programme puisse accéder à une partie d'une page virtuelle, la page doit à ce moment-là être sauvegardée par («connectée à») une trame réelle. Mais comme il y a généralement beaucoup plus de mémoire virtuelle que de mémoire réelle, les pages doivent se déplacer régulièrement entre la mémoire réelle et la mémoire de sauvegarde, entrant dans la mémoire réelle lorsqu'un processus a besoin d'y accéder, puis se repliant vers la mémoire de sauvegarde lorsqu'elles ne sont plus nécessaires. Ce mouvement est appelé pagination.

Lorsqu'un programme tente d'accéder à une page n'étant pas sauvegardée par la mémoire réelle à ce moment-là, on parle d'erreur de page. Lorsqu'une erreur de page se produit, le noyau suspend le processus, place la page dans une trame de page réelle (c'est ce qu'on appelle «pagination» ou «erreur d'entrée»), puis reprend le processus de sorte que du point de vue du processus, la page était en mémoire réelle depuis le début. En fait, pour le processus, toutes les pages semblent toujours être en mémoire réelle. Sauf pour une chose : le temps d'exécution écoulé d'une instruction qui serait normalement de quelques nanosecondes est soudainement beaucoup plus long (car le noyau doit normalement effectuer des entrées/sorties pour terminer la page). Pour les programmes sensibles à cela, les fonctions décrites dans Verrouillage des pages peuvent le contrôler.

Dans chaque espace d'adressage virtuel, un processus doit garder une trace de ce qui se trouve à quelles adresses, et ce processus est appelé allocation de mémoire. L'allocation évoque généralement la répartition des ressources rares, mais dans le cas de la mémoire virtuelle, ce n'est pas un objectif majeur, car il y en a généralement beaucoup plus que ce dont chacun a besoin. L'allocation de mémoire au sein d'un processus consiste principalement à s'assurer que le même octet de mémoire n'est pas utilisé pour entreposer deux choses différentes.

Les processus allouent de la mémoire de deux manières principales : par exec et par programmation. En fait, le forking est une troisième façon, mais elle n'est pas très intéressante.

exec est l'opération de création d'un espace d'adressage virtuel pour un processus, de chargement de son programme de base dans celui-ci et d'exécution du programme. Elle est effectuée par la famille de fonctions «exec» (par exemple execl). L'opération prend un fichier programme (un exécutable), alloue de l'espace pour charger toutes les données de l'exécutable, le charge et lui transfère le contrôle. Ces données sont notamment les instructions du programme (le texte), mais aussi les littéraux et les constantes du programme et même certaines variables : les variables C avec la classe d'entreposage statique.

Une fois que ce programme commence à s'exécuter, il utilise l'allocation programmatique pour gagner de la mémoire supplémentaire. Dans un programme C avec la bibliothèque glibc, il existe deux types d'allocation programmatique : automatique et dynamique.

Les entrées/sorties cartographiées en mémoire sont une autre forme d'allocation dynamique de mémoire virtuelle. Cartogrphier la mémoire sur un fichier signifie déclarer que le contenu d'une certaine intervalles d'adresses d'un processus doit être identique au contenu d'un fichier régulier spécifié. Le système fait en sorte que la mémoire virtuelle contienne initialement le contenu du fichier, et si vous modifiez la mémoire, le système écrit la même modification dans le fichier. Notez qu'en raison de la magie de la mémoire virtuelle et des défauts de page, il n'y a aucune raison pour que le système fasse des entrées/sorties pour lire le fichier, ou alloue de la mémoire réelle pour son contenu, jusqu'à ce que le programme accède à la mémoire virtuelle.

Tout comme il alloue de la mémoire par programmation, le programme peut la désallouer (libérer) par programmation. Vous ne pouvez pas libérer la mémoire ayant été allouée par exec. Lorsque le programme se termine ou s'exécute, vous pourriez dire que toute sa mémoire est libérée, mais comme dans les deux cas l'espace d'adressage cesse d'exister, le point est vraiment discutable.

L'espace d'adressage virtuel d'un processus est divisé en segments. Un segment est un intervalle contiguë d'adresses virtuelles. Trois segments importants sont :

Allocation d'entreposage pour les données de programme

Cette section explique comment les programmes ordinaires gèrent l'entreposage de leurs données, y compris la célèbre fonction malloc et certaines fonctionnalités plus sophistiquées spécifiques à la bibliothèque glibc et au compilateur GNU :

Allocation de mémoire dans les programmes C

Le langage de programmation C prend en charge deux types d'allocation de mémoire via les variables dans les programmes C :

Dans glibc, la taille d'entreposage automatique peut être une expression variant. Dans d'autres implémentations C, elle doit être une constante.

Un troisième type important d'allocation de mémoire, l'allocation dynamique, n'est pas pris en charge par les variables C mais est disponible via les fonctions de la bibliothèque glibc.

Allocation de mémoire dynamique

L'allocation de mémoire dynamique est une technique dans laquelle les programmes déterminent pendant leur exécution où entreposer certaines informations. Vous avez besoin d'une allocation dynamique lorsque la quantité de mémoire dont vous avez besoin, ou la durée pendant laquelle vous en avez encore besoin, dépend de facteurs n'étant pas connus avant l'exécution du programme.

Par exemple, vous pouvez avoir besoin d'un bloc pour entreposer une ligne lue dans un fichier d'entrée. Comme il n'y a pas de limite à la longueur d'une ligne, vous devez allouer la mémoire de manière dynamique et l'agrandir de manière dynamique à mesure que vous lisez davantage de la ligne.

Vous pouvez également avoir besoin d'un bloc pour chaque enregistrement ou chaque définition dans les données d'entrée. Comme vous ne pouvez pas savoir à l'avance combien il y en aura, vous devez allouer un nouveau bloc pour chaque enregistrement ou définition au fur et à mesure que vous le lisez.

Lorsque vous utilisez l'allocation dynamique, l'allocation d'un bloc de mémoire est une action que le programme demande explicitement. Vous appelez une fonction ou une macro lorsque vous souhaitez allouer de l'espace et spécifiez la taille avec un paramètre. Si vous souhaitez libérer de l'espace, vous le faites en appelant une autre fonction ou macro. Vous pouvez faire ces choses quand vous le souhaitez, aussi souvent que vous le souhaitez.

L'allocation dynamique n'est pas prise en charge par les variables C; il n'existe pas de classe d'entreposage «dynamique», et il ne peut jamais y avoir de variable C dont la valeur est stockée dans un espace alloué dynamiquement. La seule façon d'obtenir une mémoire allouée dynamiquement est via un appel système (se faisant généralement via un appel de fonction de la bibliothèque glibc), et la seule façon de faire référence à un espace alloué dynamiquement est via un pointeur. Parce que c'est moins pratique et parce que le processus d'allocation dynamique nécessite plus de temps de calcul, les programmeurs n'utilisent généralement l'allocation dynamique que lorsque ni l'allocation statique ni l'allocation automatique ne suffisent.

Par exemple, si vous souhaitez allouer dynamiquement de l'espace pour contenir une structure foobar, vous ne pouvez pas déclarer une variable de type struct foobar dont le contenu est l'espace alloué dynamiquement. Mais vous pouvez déclarer une variable de type pointeur struct foobar * et lui attribuer l'adresse de l'espace. Vous pouvez ensuite utiliser les opérateurs «*» et «->» sur cette variable pointeur pour faire référence au contenu de l'espace :

  1. {
  2.   struct foobar *ptr = malloc (sizeof *ptr);
  3.   ptr->name = x;
  4.   ptr->next = current_foobar;
  5.   current_foobar = ptr;
  6. }

L'allocateur GNU

L'implémentation de malloc dans la bibliothèque glibc est dérivée de ptmalloc (pthreads malloc), qui à son tour est dérivé de dlmalloc (Doug Lea malloc). Ce malloc peut allouer de la mémoire de deux manières différentes en fonction de leur taille et de certains paramètres pouvant être contrôlés par les utilisateurs. La manière la plus courante consiste à allouer des portions de mémoire (appelées chunks) à partir d'une grande zone contiguë de mémoire et à gérer ces zones pour optimiser leur utilisation et réduire le gaspillage sous la forme de chunks inutilisables. Traditionnellement, la mémoire de tas système était configuré pour être la seule grande zone de mémoire, mais l'implémentation de malloc de la bibliothèque glibc maintient plusieurs de ces zones pour optimiser leur utilisation dans les applications multi-processus léger. Chacune de ces zones est appelée en interne une arène.

Contrairement aux autres versions, le malloc de la bibliothèque glibc n'arrondit pas les tailles de chunks à des puissances de deux, ni pour les grandes ni pour les petites tailles. Les chunks voisins peuvent être fusionnés sur un free quelle que soit leur taille. Cela rend l'implémentation adaptée à tous les types de modèles d'allocation sans entraîner généralement un gaspillage de mémoire élevé par fragmentation. La présence de plusieurs arènes permet à plusieurs processus légers d'allouer simultanément de la mémoire dans des arènes séparées, améliorant ainsi les performances.

L'autre façon d'allouer de la mémoire est pour les blocs très volumineux, c'est-à-dire beaucoup plus grands qu'une page. Ces requêtes sont allouées avec mmap (anonyme ou via /dev/zero). Cela présente le grand avantage que ces morceaux sont renvoyés au système immédiatement lorsqu'ils sont libérés. Par conséquent, il ne peut pas arriver qu'un gros morceau soit «bloqué» entre des morceaux plus petits et même après avoir appelé free gaspille de la mémoire. Le seuil de taille pour que mmap soit utilisé est dynamique et est ajusté en fonction des modèles d'allocation du programme. mallopt peut être utilisé pour ajuster statiquement le seuil à l'aide de M_MMAP_THRESHOLD et l'utilisation de mmap peut être complètement désactivée avec M_MMAP_MAX.

Il est possible d'utiliser votre propre malloc personnalisé au lieu de l'allocateur intégré fourni par la bibliothèque glibc.

Allocation sans contrainte

La fonction d'allocation dynamique la plus générale est malloc. Elle vous permet d'allouer des blocs de mémoire de n'importe quelle taille à tout moment, de les agrandir ou de les réduire à tout moment et de libérer les blocs individuellement à tout moment (ou jamais) :

Allocation de mémoire de base

Pour allouer un bloc de mémoire, appelez malloc. Le prototype de cette fonction se trouve dans stdlib.h.

Fonction :

void * malloc (size_t size)

Préliminaire : | MT-Safe | AS-Unsafe lock | AC-Unsafe lock fd mem

Cette fonction renvoie un pointeur vers un bloc nouvellement alloué d'une taille de octets de long, ou un pointeur null (définissant errno) si le bloc n'a pas pu être alloué.

Le contenu du bloc n'est pas défini ; vous devez l'initialiser vous-même (ou utiliser calloc à la place). Normalement, vous convertissez la valeur en un pointeur vers le type d'objet que vous souhaitez stocker dans le bloc. Nous montrons ici un exemple de cette méthode et d'initialisation de l'espace avec des zéros à l'aide de la fonction de bibliothèque memset :

  1. struct foo *ptr = malloc (sizeof *ptr);
  2. if (ptr == 0) abort ();
  3. memset (ptr, 0, sizeof (struct foo));

Vous pouvez entreposer le résultat de malloc dans n'importe quelle variable de pointeur sans conversion, car le ISO C convertit automatiquement le type void * en un autre type de pointeur lorsque cela est nécessaire. Cependant, une conversion est nécessaire si le type est nécessaire mais n'est pas spécifié par le contexte.

N'oubliez pas que lors de l'allocation d'espace pour une chaîne de caractères, le paramètre de malloc doit être un plus la longueur de la chaîne de caractères. En effet, une chaîne se termine par un caractère null ne comptant pas dans la «longueur» de la chaîne de caractères mais ayant besoin d'espace. Par exemple :

  1. char *ptr = malloc (length + 1);

Exemples de malloc

Si plus d'espace n'est disponible, malloc renvoie un pointeur null. Vous devez vérifier la valeur de chaque appel à malloc. Il est utile d'écrire une sous-routine appelant malloc et signale une erreur si la valeur est un pointeur null, ne renvoyant que si la valeur est différente de zéro. Cette fonction est traditionnellement appelée xmalloc. La voici :

  1. void * xmalloc (size_t size) {
  2.   void *value = malloc (size);
  3.   if (value == 0) fatal ("mémoire virtuelle épuisée");
  4.   return value;
  5. }

Voici un exemple réel d'utilisation de malloc (via xmalloc). La fonction savestring va copier une séquence de caractères dans une chaîne de caractères nouvellement allouée terminée par un caractère null :

  1. char * savestring (const char *ptr, size_t len) {
  2.   char *value = xmalloc (len + 1);
  3.   value[len] = '\0';
  4.   return memcpy (value, ptr, len);
  5. }

Le bloc que malloc vous donne est assuré d'être aligné de manière à pouvoir contenir n'importe quel type de données. Sur les systèmes GNU, l'adresse est toujours un multiple de huit sur les systèmes 32 bits et un multiple de 16 sur les systèmes 64 bits. Une limite supérieure (comme une limite de page) n'est que rarement nécessaire ; dans ces cas, utilisez aligned_alloc ou posix_memalign.

Notez que la mémoire située après la fin du bloc est susceptible d'être utilisée pour autre chose ; peut-être un bloc déjà alloué par un autre appel à malloc. Si vous essayez de traiter le bloc comme étant plus long que ce que vous avez demandé, vous risquez de détruire les données que malloc utilise pour garder la trace de ses blocs, ou vous risquez de détruire le contenu d'un autre bloc. Si vous avez déjà alloué un bloc et que vous découvrez que vous voulez qu'il soit plus grand, utilisez realloc.

Notes de portabilité :

Libérer la mémoire allouée avec malloc

Lorsque vous n'avez plus besoin d'un bloc obtenu avec malloc, utilisez la fonction free pour rendre le bloc disponible pour être à nouveau alloué. Le prototype de cette fonction se trouve dans stdlib.h.

Fonction :

void free (void *ptr)

Préliminaire : | MT-Safe | AS-Unsafe lock | AC-Unsafe lock fd mem

La fonction free libère le bloc de mémoire pointé par ptr.

La libération d'un bloc modifie le contenu du bloc. Ne vous attendez pas à trouver des données (comme un pointeur vers le bloc suivant dans une chaîne de blocs) dans le bloc après l'avoir libéré. ??Copiez tout ce dont vous avez besoin hors du bloc avant de le libérer ! Voici un exemple de la bonne façon de libérer tous les blocs d'une chaîne de caractères et les chaînes vers lesquelles ils pointent :

  1. struct chain   {
  2.  struct chain *next;
  3.  char *name;
  4. }
  5.  
  6. void free_chain (struct chain *chain) {
  7.  while (chain != 0) {
  8.   struct chain *next = chain->next;
  9.   free(chain->name);
  10.   free(chain);
  11.   chain = next;
  12.  }
  13. }

Parfois, free peut réellement restituer de la mémoire au système d'exploitation et réduire la taille du processus. En général, tout ce qu'il peut faire est d'autoriser un appel ultérieur à malloc pour réutiliser l'espace. En attendant, l'espace reste dans votre programme en tant que partie d'une liste libre utilisée en interne par malloc.

La fonction free préserve la valeur de errno, de sorte que le code de nettoyage n'a pas à se soucier de sauvegarder et de restaurer errno autour d'un appel à free. Bien que ni ISO C ni POSIX.1-2017 n'exigent que free préserve errno, une future version de POSIX est prévue pour l'exiger.

Il n'y a aucun intérêt à libérer des blocs à la fin d'un programme, car tout l'espace du programme est restitué au système lorsque le processus se termine.

Modification de la taille d'un bloc

Vous ne savez souvent pas avec certitude quelle taille de bloc vous aurez besoin au moment où vous devez commencer à utiliser le bloc. Par exemple, le bloc peut être un tampon que vous utilisez pour contenir une ligne lue dans un fichier ; quelle que soit la longueur initiale du tampon, vous pouvez rencontrer une ligne plus longue.

Vous pouvez allonger le bloc en appelant realloc ou reallocarray. Ces fonctions sont déclarées dans stdlib.h.

Fonction :

void * realloc(void *ptr, size_t newsize)

Préliminaire : | MT-Safe | AS-Unsafe lock | AC-Unsafe lock fd mem

La fonction realloc modifie la taille du bloc dont l'adresse est ptr pour qu'elle soit newsize.

Étant donné que l'espace après la fin du bloc peut être utilisé, realloc peut trouver nécessaire de copier le bloc vers une nouvelle adresse où plus d'espace libre est disponible. La valeur de realloc est la nouvelle adresse du bloc. Si le bloc doit être déplacé, realloc copie l'ancien contenu.

Si vous passez un pointeur null pour ptr, realloc se comporte comme «malloc (newsize)». Sinon, si newsize est zéro, realloc libère le bloc et renvoie NULL. Sinon, si realloc ne peut pas réallouer la taille demandée, il renvoie NULL et définit errno ; le bloc d'origine n'est pas perturbé.

Fonction :

void * reallocarray (void *ptr, size_t nmemb, size_t size)

Préliminaire : | MT-Safe | AS-Unsafe lock | AC-Unsafe lock fd mem

La fonction reallocarray modifie la taille du bloc dont l'adresse est ptr pour qu'il soit suffisamment long pour contenir un vecteur de nmemb éléments, chacun de taille size. Elle est équivalente à «realloc (ptr, nmemb * size)», sauf que reallocarray échoue en toute sécurité si la multiplication déborde, en définissant errno sur ENOMEM, en renvoyant un pointeur null et en laissant le bloc d'origine inchangé.

reallocarray doit être utilisé à la place de realloc lorsque la nouvelle taille du bloc alloué est le résultat d'une multiplication pouvant déborder.

Note de portabilité : cette fonction ne fait partie d'aucune norme. Elle a été introduite pour la première fois dans OpenBSD 5.6.

Comme malloc, realloc et reallocarray peuvent renvoyer un pointeur null si aucun espace mémoire n'est disponible pour agrandir le bloc. Lorsque cela se produit, le bloc d'origine n'est pas touché ; il n'a pas été modifié ou déplacé.

Dans la plupart des cas, le sort du bloc d'origine ne fait aucune différence lorsque la réallocation échoue, car le programme d'application ne peut pas continuer lorsqu'il n'a plus de mémoire et la seule chose à faire est de donner un message d'erreur fatal. Il est souvent pratique d'écrire et d'utiliser des sous-routines, appelées conventionnellement xrealloc et xreallocarray, prenant en charge le message d'erreur comme le fait xmalloc pour malloc :

  1. void * xreallocarray (void *ptr, size_t nmemb, size_t size) {
  2.  void *value = reallocarray (ptr, nmemb, size);
  3.  if (value == 0) fatal ("Mémoire virtuelle épuisée");
  4.  return value;
  5. }
  6.  
  7. void * xrealloc (void *ptr, size_t size) {
  8.  return xreallocarray (ptr, 1, size);
  9. }

Vous pouvez également utiliser realloc ou reallocarray pour réduire la taille d'un bloc. La raison pour laquelle vous feriez cela est d'éviter de bloquer beaucoup d'espace mémoire alors que seulement un peu est nécessaire. Dans plusieurs implémentations d'allocation, réduire la taille d'un bloc nécessite parfois de le copier, donc cela peut échouer si aucun autre espace n'est disponible.

Notes de portabilité :

Allocation d'espace libéré

La fonction calloc alloue de la mémoire et la remet à zéro. Elle est déclarée dans stdlib.h.

Fonction :

void * calloc (size_t count, size_t eltsize)

Préliminaire : | MT-Safe | AS-Unsafe lock | AC-Unsafe lock fd mem

Cette fonction alloue un bloc suffisamment long pour contenir un vecteur de count éléments, chacun de taille eltsize. Son contenu est remis à zéro avant le retour de calloc.

Vous pouvez définir calloc comme suit :

  1. void * calloc(size_t count, size_t eltsize) {
  2.  void *value = reallocarray(0, count, eltsize);
  3.  if (value != 0) memset(value, 0, count * eltsize);
  4.  return value;
  5. }

Mais en général, il n'est pas garanti que calloc appelle reallocarray et memset en interne. Par exemple, si l'implémentation de calloc sait pour d'autres raisons que le nouveau bloc de mémoire est nul, elle n'a pas besoin de remettre le bloc à zéro avec memset. De plus, si une application fournit son propre reallocarray en dehors de la bibliothèque C, calloc peut ne pas utiliser cette redéfinition.

Allocation de blocs de mémoire alignés

L'adresse d'un bloc renvoyée par malloc ou realloc dans les systèmes GNU est toujours un multiple de huit (ou seize sur les systèmes 64 bits). Si vous avez besoin d'un bloc dont l'adresse est un multiple d'une puissance de deux supérieure à celle-ci, utilisez aligned_alloc ou posix_memalign. aligned_alloc et posix_memalign sont déclarés dans stdlib.h.

Fonction :

void * aligned_alloc (size_t alignment, size_t size)

Préliminaire : | MT-Safe | AS-Unsafe lock | AC-Unsafe lock fd mem

La fonction aligned_alloc alloue un bloc de size octets dont l'adresse est un multiple de l'alignement. L'alignement doit être une puissance de deux.

La fonction aligned_alloc renvoie un pointeur null en cas d'erreur et définit errno sur l'une des valeurs suivantes :

Constante Description
ENOMEM La mémoire disponible était insuffisante pour satisfaire la demande.
EINVAL alignment n'est pas une puissance de deux.

Cette fonction a été introduite dans ISO C11 et peut donc avoir une meilleure portabilité vers les systèmes non-POSIX modernes que posix_memalign.

Fonction :

void * memalign (size_t boundary, size_t size)

Préliminaire : | MT-Safe | AS-Unsafe lock | AC-Unsafe lock fd mem

La fonction memalign alloue un bloc de taille octets dont l'adresse est un multiple de la limite. La limite doit être une puissance de deux ! La fonction memalign fonctionne en allouant un bloc un peu plus grand, puis en renvoyant une adresse dans le bloc se trouvant sur la limite spécifiée.

La fonction memalign renvoie un pointeur nul en cas d'erreur et définit errno sur l'une des valeurs suivantes :

Constante Description
ENOMEM La mémoire disponible était insuffisante pour satisfaire la demande.
EINVAL boundary n'est pas une puissance de deux.

La fonction memalign est obsolète et il faut utiliser aligned_alloc ou posix_memalign à la place.

Fonction :

int posix_memalign (void **memptr, size_t alignment, size_t size)

Préliminaire : | MT-Safe | AS-Unsafe lock | AC-Unsafe lock fd mem

La fonction posix_memalign est similaire à la fonction memalign dans la mesure où elle renvoie un tampon de size octets aligné sur un multiple de alignment. Mais elle ajoute une exigence au paramètre alignment : la valeur doit être une puissance de deux multiples de sizeof(void *).

Si la fonction réussit à allouer de la mémoire, un pointeur vers la mémoire allouée est renvoyé dans *memptr et la valeur de retour est zéro. Sinon, la fonction renvoie une valeur d'erreur indiquant le problème. Les valeurs d'erreur possibles renvoyées sont :

Constante Description
ENOMEM La mémoire disponible était insuffisante pour satisfaire la demande.
EINVAL alignment n'est pas une puissance de deux multiple de sizeof (void *).

Cette fonction a été introduite dans POSIX 1003.1d. Bien que cette fonction soit remplacée par aligned_alloc, elle est plus portable sur les anciens systèmes POSIX ne prenant pas en charge ISO C11.

Fonction :

void * valloc (size_t size)

Préliminaire : | MT-Unsafe init | AS-Unsafe init lock | AC-Unsafe init lock fd mem.

L'utilisation de valloc est similaire à l'utilisation de memalign et à la transmission de la taille de la page comme valeur du premier paramètre. Elle est implémentée comme ceci :

  1. void * valloc (size_t size) {
  2.   return memalign (getpagesize (), size);
  3. }

Comment obtenir des informations sur le sous-système de mémoire ? pour plus d'informations sur le sous-système de mémoire. La fonction valloc est obsolète et il faut utiliser aligned_alloc ou posix_memalign à la place.

Paramètres réglables de malloc

Vous pouvez ajuster certains paramètres pour l'allocation dynamique de mémoire avec la fonction mallopt. Cette fonction est l'interface SVID/XPG générale, définie dans malloc.h.

Fonction :

int mallopt(int param, int value)

Préliminaire : | MT-Unsafe init const:mallopt | AS-Unsafe init lock | AC-Unsafe init lock

Lors de l'appel de mallopt, le paramètre param spécifie le paramètre à définir et value la nouvelle valeur à définir. Les choix possibles pour param, tels que définis dans malloc.h, sont :

Constante Description
M_MMAP_MAX Nombre maximal de blocs à allouer avec mmap. La définition de ce paramètre sur zéro désactive toute utilisation de mmap. La valeur par défaut de ce paramètre est 65536. Ce paramètre peut également être défini pour le processus au démarrage en définissant la variable d'environnement MALLOC_MMAP_MAX_ sur la valeur souhaitée.
M_MMAP_THRESHOLD Tous les segments supérieurs à cette valeur sont alloués en dehors du tas normal, à l'aide de l'appel système mmap. De cette façon, il est garanti que la mémoire de ces segments peut être renvoyée au système en cas de libération. Notez que les requêtes inférieures à ce seuil peuvent toujours être allouées via mmap. Si ce paramètre n'est pas défini, la valeur par défaut est définie sur 128 Ko et le seuil est ajusté dynamiquement pour s'adapter aux modèles d'allocation du programme. Si le paramètre est défini, l'ajustement dynamique est désactivé et la valeur est définie statiquement sur la valeur d'entrée. Ce paramètre peut également être défini pour le processus au démarrage en définissant la variable d'environnement MALLOC_MMAP_THRESHOLD_ sur la valeur souhaitée.
M_PERTURB Si la valeur est différente de zéro, les blocs de mémoire sont remplis avec des valeurs dépendant de certains bits de poids faible de ce paramètre lorsqu'ils sont alloués (sauf lorsqu'ils sont alloués par calloc) et libérés. Cela peut être utilisé pour déboguer l'utilisation de la mémoire de tas non initialisée ou libérée. Notez que cette option ne garantit pas que le bloc libéré aura des valeurs spécifiques. Elle garantit seulement que le contenu du bloc avant sa libération sera écrasé. La valeur par défaut de ce paramètre est 0. Ce paramètre peut également être défini pour le processus au démarrage en définissant la variable d'environnement MALLOC_PERTURB_ sur la valeur souhaitée.
M_TOP_PAD Ce paramètre détermine la quantité de mémoire supplémentaire à obtenir du système lorsqu'une arène doit être agrandie. Il spécifie également le nombre d'octets à conserver lors de la réduction d'une arène. Cela fournit l'hystérésis nécessaire dans la taille du tas pour éviter des quantités excessives d'appels système. La valeur par défaut de ce paramètre est 0. Ce paramètre peut également être défini pour le processus au démarrage en définissant la variable d'environnement MALLOC_TOP_PAD_ sur la valeur souhaitée.
M_TRIM_THRESHOLD Il s'agit de la taille minimale (en octets) du bloc libérable le plus haut déclenchant un appel système afin de restituer la mémoire au système. Si ce paramètre n'est pas défini, la valeur par défaut est définie sur 128 Ko et le seuil est ajusté de manière dynamique pour s'adapter aux modèles d'allocation du programme. Si le paramètre est défini, l'ajustement dynamique est désactivé et la valeur est définie de manière statique sur l'entrée fournie. Ce paramètre peut également être défini pour le processus au démarrage en définissant la variable d'environnement MALLOC_TRIM_THRESHOLD_ sur la valeur souhaitée.
M_ARENA_TEST Ce paramètre spécifie le nombre d'arènes pouvant être créées avant que le test sur la limite du nombre d'arènes ne soit effectué. La valeur est ignorée si M_ARENA_MAX est défini. La valeur par défaut de ce paramètre est 2 sur les systèmes 32 bits et 8 sur les systèmes 64 bits. Ce paramètre peut également être défini pour le processus au démarrage en définissant la variable d'environnement MALLOC_ARENA_TEST sur la valeur souhaitée.
M_ARENA_MAX Ce paramètre définit le nombre d'arènes à utiliser quel que soit le nombre de cours du système. La valeur par défaut de ce paramètre est 0, ce qui signifie que la limite du nombre d'arènes est déterminée par le nombre de cours de processeur en ligne. Pour les systèmes 32 bits, la limite est deux fois supérieure au nombre de coeurs en ligne et sur les systèmes 64 bits, elle est huit fois supérieure au nombre de coeurs en ligne. Notez que la valeur par défaut n'est pas dérivée de la valeur par défaut de M_ARENA_TEST et est calculée indépendamment. Ce paramètre peut également être défini pour le processus au démarrage en définissant la variable d'environnement MALLOC_ARENA_MAX sur la valeur souhaitée.

Vérification de la cohérence de la mémoire de tas

Vous pouvez demander à malloc de vérifier la cohérence de la mémoire dynamique en utilisant la fonction mcheck et en préchargeant la bibliothèque de débogage malloc libc_malloc_debug à l'aide de la variable d'environnement LD_PRELOAD. Cette fonction est une extension GNU, déclarée dans mcheck.h.

Fonction :

int mcheck (void (*abortfn) (enum mcheck_status status))

Préliminaire : | MT-Unsafe race:mcheck const:malloc_hooks | AS-Unsafe corrupt | AC-Unsafe corrupt

L'appel de mcheck indique à malloc d'effectuer des contrôles de cohérence occasionnels. Ceux-ci détecteront des choses comme l'écriture au-delà de la fin d'un bloc ayant été alloué avec malloc.

Le paramètre abortfn est la fonction à appeler lorsqu'une incohérence est trouvée. Si vous fournissez un pointeur nul, alors mcheck utilise une fonction par défaut affichant un message et appelle abort. La fonction que vous fournissez est appelée avec un paramètre, indiquant quel type d'incohérence a été détecté; son type est décrit ci-dessous.

Il est trop tard pour commencer la vérification d'allocation une fois que vous avez alloué quoi que ce soit avec malloc. Donc mcheck ne fait rien dans ce cas. La fonction renvoie -1 si vous l'appelez trop tard, et 0 sinon (quand elle réussit).

Le moyen le plus simple de s'arranger pour appeler mcheck suffisamment tôt est d'utiliser l'option «-lmcheck» lorsque vous liez votre programme; alors vous n'avez pas besoin de modifier du tout la source de votre programme. Vous pouvez également utiliser un débogueur pour insérer un appel à mcheck à chaque démarrage du programme, par exemple ces commandes gdb appelleront automatiquement mcheck à chaque démarrage du programme :

(gdb) break main
Breakpoint 1, main (argc=2, argv=0xbffff964) at whatever.c:10
(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>call mcheck(0)
>continue
>end
(gdb) ...

Cela ne fonctionnera cependant que si aucune fonction d'initialisation d'un objet impliqué n'appelle l'une des fonctions malloc puisque mcheck doit être appelé avant la première fonction de ce type.

Fonction :

enum mcheck_status mprobe(void *pointer)

Préliminaire : | MT-Unsafe race:mcheck const:malloc_hooks | AS-Unsafe corrupt | AC-Unsafe corrupt

La fonction mprobe vous permet de vérifier explicitement les incohérences dans un bloc alloué particulier. Vous devez déjà avoir appelé mcheck au début du programme, pour effectuer ses vérifications occasionnelles ; l'appel de mprobe demande qu'une vérification de cohérence supplémentaire soit effectuée au moment de l'appel.

Le pointeur de paramètre doit être un pointeur renvoyé par malloc ou realloc. mprobe renvoie une valeur indiquant quelle incohérence, le cas échéant, a été trouvée. Les valeurs sont décrites ci-dessous.

Type de données :

enum mcheck_status

Ce type énuméré décrit le type d'incohérence détecté dans un bloc alloué, le cas échéant. Voici les valeurs possibles :

Valeur Description
MCHECK_DISABLED mcheck n'a pas été appelé avant la première allocation. Aucune vérification de cohérence ne peut être effectuée.
MCHECK_OK Aucune incohérence détectée.
MCHECK_HEAD Les données immédiatement avant le bloc ont été modifiées. Cela se produit généralement lorsqu'un index ou un pointeur de tableau est décrémenté trop loin.
MCHECK_TAIL Les données immédiatement après la modification du bloc. Cela se produit généralement lorsqu'un index ou un pointeur de tableau est incrémenté trop loin.
MCHECK_FREE Le bloc était déjà libéré.

Une autre possibilité de vérifier et de se protéger contre les bogues dans l'utilisation de malloc, realloc et free est de définir la variable d'environnement MALLOC_CHECK_. Lorsque MALLOC_CHECK_ est défini sur une valeur non nulle inférieure à 4, une implémentation spéciale (moins efficace) est utilisée, conçue pour être tolérante aux erreurs simples, telles que les doubles appels de free avec le même paramètre, ou les dépassements d'un seul octet (bogues de déplacement d'un octet). Cependant, il n'est pas possible de se protéger contre toutes ces erreurs, et des fuites de mémoire peuvent en résulter. Comme dans le cas de mcheck, il faudrait précharger la bibliothèque libc_malloc_debug pour activer la fonctionnalité MALLOC_CHECK_. Sans cette bibliothèque préchargée, le paramétrage de MALLOC_CHECK_ n'aura aucun effet.

Toute corruption de mémoire de tas détectée entraîne l'arrêt immédiat du processus.

Il y a un problème avec MALLOC_CHECK_ : dans les binaires SUID ou SGID, il pourrait être exploité car, en divergeant du comportement normal des programmes, il écrit maintenant quelque chose dans le descripteur d'erreur standard. Par conséquent, l'utilisation de MALLOC_CHECK_ est désactivée par défaut pour les binaires SUID et SGID.

Alors, quelle est la différence entre l'utilisation de MALLOC_CHECK_ et la liaison avec « -lmcheck » ? MALLOC_CHECK_ est orthogonal par rapport à «-lmcheck». «-lmcheck» a été ajouté pour la compatibilité ascendante. MALLOC_CHECK_ et « -lmcheck » devraient tous deux découvrir les mêmes bogues - mais en utilisant MALLOC_CHECK_, vous n'avez pas besoin de recompiler votre application.

Statistiques sur l'allocation de mémoire avec malloc

Vous pouvez obtenir des informations sur l'allocation de mémoire dynamique en appelant la fonction mallinfo2. Cette fonction et son type de données associé sont déclarés dans malloc.h; ils constituent une extension de la version standard SVID/XPG.

Type de données :

struct mallinfo2

Ce type de structure est utilisé pour renvoyer des informations sur l'allocateur de mémoire dynamique. Il contient les membres suivants :

Champ Description
size_t arena Il s'agit de la taille totale de la mémoire allouée avec sbrk par malloc, en octets.
size_t ordblks Il s'agit du nombre de blocs non utilisés. (L'allocateur de mémoire size_ternally obtient des blocs de mémoire du système d'exploitation, puis les découpe pour satisfaire les requêtes malloc individuelles).
size_t smblks Ce champ n'est pas utilisé.
size_t hblks Il s'agit du nombre total de blocs alloués avec mmap.
size_t hblkhd Il s'agit de la taille totale de la mémoire allouée avec mmap, en octets.
size_t usmblks Ce champ est inutilisé et toujours à 0.
size_t fsmblks Ce champ n'est pas utilisé.
size_t uordblks Il s'agit de la taille totale de la mémoire occupée par les blocs distribués par malloc.
size_t fordblks Il s'agit de la taille totale de la mémoire occupée par les blocs libres (non utilisés).
size_t keepcost Il s'agit de la taille du bloc libérable le plus haut qui borde normalement la fin du tas (c'est-à-dire l'extrémité supérieure du segment de données de l'espace d'adressage virtuel).

Fonction :

struct mallinfo2 mallinfo2 (void)

Préliminaire : | MT-Unsafe init const:mallopt | AS-Unsafe init lock | AC-Unsafe init lock

Cette fonction renvoie des informations sur l'utilisation actuelle de la mémoire dynamique dans une structure de type struct mallinfo2.

Résumé des fonctions liées à malloc

Voici un résumé des fonctions fonctionnant avec malloc :

void *malloc (size_t size)

Allouer un bloc de taille octets.

void free (void *addr)

Libérez un bloc précédemment alloué par malloc.

void *realloc (void *addr, size_t size)

Augmentez ou diminuez la taille d'un bloc précédemment alloué par malloc, éventuellement en le copiant vers un nouvel emplacement.

void *reallocarray (void *ptr, size_t nmemb, size_t size)

Modifiez la taille d'un bloc précédemment alloué par malloc à nmemb * size octets comme avec realloc.

void *calloc(size_t count, size_t eltsize)

Allouez un bloc de count * eltsize octets à l'aide de malloc et définissez son contenu sur zéro.

void *valloc (size_t size)

Allouer un bloc de taille octets, en commençant par une limite de page. Voir Allocation de blocs de mémoire alignés.

void *aligned_alloc (size_t alignment, size_t size)

Allouer un bloc de taille octets, en commençant par une adresse qui est un multiple de l'alignement.

int posix_memalign (void **memptr, size_t alignment, size_t size)

Allouer un bloc de taille octets, en commençant par une adresse qui est un multiple de l'alignement.

void *memalign (size_t boundary, size_t size)

Allouer un bloc de taille octets, en commençant par une adresse qui est un multiple de la limite.

int mallopt (int param, int value)

Ajuster un paramètre réglable.

int mcheck (void (*abortfn) (void))

Indiquez à malloc d'effectuer des vérifications de cohérence occasionnelles sur la mémoire allouée dynamiquement et d'appeler abortfn lorsqu'une incohérence est détectée.

struct mallinfo2 mallinfo2 (void)

Renvoie des informations sur l'utilisation actuelle de la mémoire dynamique.

Débogage d'allocation

Une tâche compliquée lors de la programmation avec des langages n'utilisant pas l'allocation dynamique de mémoire par ramasse-miettes est de trouver des fuites de mémoire. Les programmes à exécution longue doivent s'assurer que les objets alloués dynamiquement sont libérés à la fin de leur durée de vie. Si cela ne se produit pas, le système se retrouve à court de mémoire, tôt ou tard.

L'implémentation de malloc dans la bibliothèque glibc fournit des moyens simples pour détecter de telles fuites et obtenir des informations pour trouver l'emplacement. Pour ce faire, l'application doit être démarrée dans un mode spécial qui est activé par une variable d'environnement. Il n'y a pas de pénalités de vitesse pour le programme si le mode de débogage n'est pas activé.

Comment installer la fonctionnalité de traçage

Fonction :

void mtrace(void)

Préliminaire : | MT-Unsafe env race:mtrace init | AS-Unsafe init heap corrupt lock | AC-Unsafe init corrupt lock fd mem.

La fonction mtrace permet de tracer les événements d'allocation de mémoire dans le programme l'appelant. Elle est désactivée par défaut dans la bibliothèque et peut être activée en préchargeant la bibliothèque de débogage libc_malloc_debug à l'aide de la variable d'environnement LD_PRELOAD.

Lorsque la fonction mtrace est appelée, elle recherche une variable d'environnement nommée MALLOC_TRACE. Cette variable est censée contenir un nom de fichier valide. L'utilisateur doit avoir un accès en écriture. Si le fichier existe déjà, il est tronqué. Si la variable d'environnement n'est pas définie ou ne nomme pas un fichier valide qui peut être ouvert en écriture, rien n'est fait. Le comportement de malloc,... n'est pas modifié. Pour des raisons évidentes, cela se produit également si l'application est installée avec le bit SUID ou SGID défini.

Si le fichier nommé est ouvert avec succès, mtrace installe des gestionnaires spéciaux pour les fonctions malloc, realloc et free. À partir de là, toutes les utilisations de ces fonctions sont tracées et enregistrées dans le fichier. Il y a bien sûr maintenant une pénalité de vitesse pour tous les appels aux fonctions tracées, donc le traçage ne doit pas être activé pendant une utilisation normale.

Cette fonction est une extension GNU et n'est généralement pas disponible sur d'autres systèmes. Le prototype peut être trouvé dans mcheck.h.

Fonction :

void muntrace (void)

Préliminaire : | MT-Unsafe race:mtrace locale | AS-Unsafe corrupt heap | AC-Unsafe corrupt mem lock fd

La fonction muntrace peut être appelée après que mtrace a été utilisé pour permettre le traçage des appels malloc. Si aucun appel (réussi) de mtrace n'a été effectué, muntrace ne fait rien.

Sinon, il désinstalle les gestionnaires pour malloc, realloc et free, puis ferme le fichier de protocole. Aucun appel n'est plus protocolé et le programme s'exécute à nouveau à pleine vitesse.

Cette fonction est une extension GNU et n'est généralement pas disponible sur d'autres systèmes. Le prototype peut être trouvé dans mcheck.h.

Extraits de programmes d'exemple

Même si la fonctionnalité de traçage n'influence pas le comportement d'exécution du programme, il n'est pas judicieux d'appeler mtrace dans tous les programmes. Imaginez que vous déboguez un programme à l'aide de mtrace et que tous les autres programmes utilisés dans la session de débogage tracent également leurs appels malloc. Le fichier de sortie serait le même pour tous les programmes et serait donc inutilisable. Par conséquent, il ne faut appeler mtrace que s'il est compilé pour le débogage. Un programme pourrait donc démarrer comme ceci :

  1. #include <mcheck.h>
  2.  
  3. int main (int argc, char *argv[]) {
  4.  #ifdef DEBUGGING
  5.   mtrace ();
  6.  #endif
  7.   ...
  8. }

C'est tout ce dont vous avez besoin si vous voulez tracer les appels pendant toute la durée d'exécution du programme. Vous pouvez également arrêter le traçage à tout moment en appelant muntrace. Il est même possible de redémarrer le traçage avec un nouvel appel à mtrace. Mais cela peut entraîner des résultats peu fiables car il peut y avoir des appels de fonctions n'étant pas appelés. Veuillez noter que non seulement l'application utilise les fonctions tracées, mais aussi les bibliothèques (y compris la bibliothèque C elle-même).

Ce dernier point est également la raison pour laquelle il n'est pas judicieux d'appeler i avant la fin du programme. Les bibliothèques sont informées de la fin du programme uniquement après que le programme revient de main ou appelle exit et ne peuvent donc pas libérer la mémoire qu'elles utilisent avant ce moment.

La meilleure chose à faire est donc d'appeler mtrace comme toute première fonction du programme et de ne jamais appeler muntrace. Ainsi, le programme trace presque toutes les utilisations des fonctions malloc (à l'exception des appels exécutés par les constructeurs du programme ou les bibliothèques utilisées).

Quelques idées plus ou moins astucieuses

Vous connaissez la situation. Le programme est préparé pour le débogage et dans toutes les sessions de débogage, il fonctionne bien. Mais une fois qu'il est démarré sans débogage, l'erreur apparaît. Un exemple typique est une fuite de mémoire qui devient visible uniquement lorsque nous désactivons le débogage. Si vous prévoyez de telles situations, vous pouvez toujours gagner. Utilisez simplement quelque chose d'équivalent au petit programme suivant :

  1. #include <mcheck.h>
  2. #include <signal.h>
  3.  
  4. static void enable (int sig) {
  5.   mtrace ();
  6.   signal (SIGUSR1, enable);
  7. }
  8.  
  9. static void disable (int sig) {
  10.   muntrace ();
  11.   signal (SIGUSR2, disable);
  12. }
  13.  
  14. int main (int argc, char *argv[]) {
  15.   ...
  16.   signal (SIGUSR1, enable);
  17.   signal (SIGUSR2, disable);
  18.   ...
  19. }

Autrement dit, l'utilisateur peut démarrer le débogueur de mémoire à tout moment s'il le souhaite si le programme a été démarré avec MALLOC_TRACE défini dans l'environnement. La sortie n'affichera bien sûr pas les allocations qui se sont produites avant le premier signal, mais s'il y a une fuite de mémoire, celle-ci apparaîtra néanmoins.

Interprétation des traces

Si vous regardez le résultat, il ressemblera à ceci :

= Start
 [0x8048209] - 0x8064cc8
 [0x8048209] - 0x8064ce0
 [0x8048209] - 0x8064cf8
 [0x80481eb] + 0x8064c48 0x14
 [0x80481eb] + 0x8064c60 0x14
 [0x80481eb] + 0x8064c78 0x14
 [0x80481eb] + 0x8064c90 0x14
= End

Ce que cela signifie n'est pas vraiment important puisque le fichier de trace n'est pas destiné à être lu par un humain. Par conséquent, aucune attention n'est accordée à la lisibilité. À la place, il existe un programme fourni avec la bibliothèque glibc interprétant les traces et génère un résumé de manière conviviale. Le programme s'appelle mtrace (il s'agit en fait d'un script Perl) et il prend un ou deux paramètres. Dans tous les cas, le nom du fichier contenant la sortie de trace doit être spécifié. Si un paramètre optionnel précède le nom du fichier de trace, il doit s'agir du nom du programme ayant généré la trace.

drepper$ mtrace tst-mtrace log
No memory leaks.

Dans ce cas, le programme tst-mtrace a été exécuté et a généré un journal de fichiers de trace. Le message affiché par mtrace montre qu'il n'y a aucun problème avec le code, toute la mémoire allouée a été libérée par la suite.

Si nous appelons mtrace sur l'exemple de trace donné ci-dessus, nous obtiendrons un résultat différent :

drepper$ mtrace errlog
- 0x08064cc8 Free 2 was never alloc'd 0x8048209
- 0x08064ce0 Free 3 was never alloc'd 0x8048209
- 0x08064cf8 Free 4 was never alloc'd 0x8048209

Memory not freed:
-----------------
   Address     Size     Caller
0x08064c48     0x14  at 0x80481eb
0x08064c60     0x14  at 0x80481eb
0x08064c78     0x14  at 0x80481eb
0x08064c90     0x14  at 0x80481eb

Nous avons appelé mtrace avec un seul paramètre et le script n'a donc aucune chance de découvrir ce que signifient les adresses données dans la trace. Nous pouvons faire mieux :

drepper$ mtrace tst errlog
- 0x08064cc8 Free 2 was never alloc'd /home/drepper/tst.c:39
- 0x08064ce0 Free 3 was never alloc'd /home/drepper/tst.c:39
- 0x08064cf8 Free 4 was never alloc'd /home/drepper/tst.c:39

Memory not freed:
-----------------
   Address     Size     Caller
0x08064c48     0x14  at /home/drepper/tst.c:33
0x08064c60     0x14  at /home/drepper/tst.c:33
0x08064c78     0x14  at /home/drepper/tst.c:33
0x08064c90     0x14  at /home/drepper/tst.c:33

Soudain, la sortie a beaucoup plus de sens et l'utilisateur peut voir immédiatement où se trouvent les appels de fonction à l'origine du problème.

L'interprétation de cette sortie n'est pas compliquée. Il y a au plus deux situations différentes détectées. Tout d'abord, free a été appelé pour des pointeurs n'ayant jamais été renvoyés par l'une des fonctions d'allocation. Il s'agit généralement d'un très grave problème et son aspect est indiqué dans les trois premières lignes de la sortie. Des situations comme celle-ci sont assez rares et si elles se produisent, elles se manifestent de manière très drastique : le programme plante généralement.

L'autre situation, beaucoup plus difficile à détecter, est celle des fuites de mémoire. Comme vous pouvez le voir dans la sortie, la fonction mtrace collecte toutes ces informations et peut donc dire que le programme appelle une fonction d'allocation de la ligne 33 du fichier source /home/drepper/tst-mtrace.c quatre fois sans libérer cette mémoire avant la fin du programme. Il reste à déterminer s'il s'agit d'un véritable problème.

Remplacement de malloc

La bibliothèque glibc prend en charge le remplacement de l'implémentation intégrée de malloc par un autre allocateur avec la même interface. Pour les programmes liés dynamiquement, cela se produit via l'interposition de symboles ELF, soit en utilisant des dépendances d'objets partagés, soit LD_PRELOAD. Pour la liaison statique, la bibliothèque de remplacement de malloc doit être liée avant la liaison avec libc.a (explicitement ou implicitement).

Il faut veiller à ne pas utiliser les fonctionnalités de la bibliothèque glibc utilisant malloc en interne. Par exemple, les fonctions fopen, opendir, dlopen et pthread_setspecific utilisent actuellement le sous-système malloc en interne. Si le malloc de remplacement ou ses dépendances utilisent l'entreposage local des processus légers (TLS), il doit utiliser le modèle TLS initial-exec, et non l'une des variantes TLS dynamiques.

Remarque : l'absence d'un ensemble complet de fonctions de remplacement (c'est-à-dire toutes les fonctions utilisées par l'application, la bibliothèque glibc et d'autres bibliothèques liées) peut entraîner des échecs de liaison statique et, au moment de l'exécution, une corruption du tas et des plantages de l'application. Les fonctions de remplacement doivent implémenter le comportement documenté pour leurs homologues dans la bibliothèque glibc; par exemple, le remplacement free doit également préserver errno.

L'ensemble minimal de fonctions qui doivent être fournies par un malloc personnalisé est indiqué dans la liste ci-dessous :

Ces fonctions liées à malloc sont nécessaires au fonctionnement de la bibliothèque glibc.

L'implémentation de malloc dans la bibliothèque glibc fournit des fonctionnalités supplémentaires n'étant pas utilisées par la bibliothèque elle-même, mais étant souvent utilisées par d'autres bibliothèques et applications système. Une implémentation de remplacement de malloc à usage général devrait également fournir des définitions de ces fonctions. Leurs noms sont répertoriés dans la liste suivant :

De plus, des applications très anciennes peuvent utiliser la fonction cfree obsolète.

D'autres fonctions liées à malloc telles que mallopt ou mallinfo2 n'auront aucun effet ou renverront des statistiques incorrectes lorsqu'un malloc de remplacement est utilisé. Cependant, le fait de ne pas remplacer ces fonctions n'entraîne généralement pas de plantages ou d'autres comportements d'application incorrects, mais peut entraîner des échecs de liaison statique.

Il existe d'autres fonctions (reallocarray, strdup,...) dans la bibliothèque glibc n'étant pas répertoriées ci-dessus mais renvoyant la mémoire nouvellement allouée aux appelants. Le remplacement de ces fonctions n'est pas pris en charge et peut produire des résultats incorrects. Les implémentations de la bibliothèque glibc de ces fonctions appellent les fonctions d'allocation de remplacement chaque fois qu'elles sont disponibles, elles fonctionneront donc correctement avec le remplacement de malloc.

Obstacks

Un obstack est un bassin de mémoire contenant une pile d'objets. Vous pouvez créer n'importe quel nombre d'obstacks distincts, puis allouer des objets dans des obstacks spécifiés. Dans chaque obstack, le dernier objet alloué doit toujours être le premier libéré, mais les obstacks distincts sont indépendants les uns des autres.

À part cette contrainte d'ordre de libération, les obstacks sont totalement généraux : un obstack peut contenir n'importe quel nombre d'objets de n'importe quelle taille. Ils sont implémentés avec des macros, donc l'allocation est généralement très rapide tant que les objets sont généralement petits. Et le seul surcoût d'espace par objet est le remplissage nécessaire pour démarrer chaque objet sur une limite appropriée.

Création d'Obstacks

Les utilitaires de manipulation d'Obstacks sont déclarés dans le fichier d'entête obstack.h.

Type de données :

struct obstack

Un obstack est représenté par une structure de données de type struct obstack. Cette structure a une petite taille fixe; elle enregistre l'état de l'obstack et comment trouver l'espace dans lequel les objets sont alloués. Elle ne contient aucun des objets eux-mêmes. Vous ne devez pas essayer d'accéder directement au contenu de la structure ; utilisez uniquement les fonctions décrites dans cette page.

Vous pouvez déclarer des variables de type struct obstack et les utiliser comme obstacks, ou vous pouvez allouer des obstacks dynamiquement comme n'importe quel autre type d'objet. L'allocation dynamique d'obstacks permet à votre programme d'avoir un nombre variable de piles différentes. (Vous pouvez même allouer une structure obstack dans un autre obstack, mais cela est rarement utile.)

Toutes les fonctions fonctionnant avec des obstacks nécessitent que vous spécifiiez quel obstack utiliser. Vous le faites avec un pointeur de type struct obstack *. Dans ce qui suit, nous disons souvent «un obstack» lorsque, à proprement parler, l'objet en question est un tel pointeur.

Les objets de l'obstack sont regroupés dans de gros blocs appelés morceaux (ou chunks en anglais). La structure struct obstack pointe vers une chaîne de morceaux actuellement utilisés.

La bibliothèque obstack obtient un nouveau morceau chaque fois que vous allouez un objet qui ne rentre pas dans le morceau précédent. Étant donné que la bibliothèque obstack gère les morceaux automatiquement, vous n'avez pas besoin d'y prêter beaucoup d'attention, mais vous devez fournir une fonction que la bibliothèque obstack doit utiliser pour obtenir un morceau. Habituellement, vous fournissez une fonction utilisant malloc directement ou indirectement. Vous devez également fournir une fonction pour libérer un morceau. Ces questions sont décrites dans la section suivante.

Préparation à l'utilisation d'Obstacks

Chaque fichier source dans lequel vous prévoyez d'utiliser les fonctions obstack doit inclure le fichier d'entête obstack.h, comme ceci :

#include <obstack.h>

De plus, si le fichier source utilise la macro obstack_init, il doit déclarer ou définir deux fonctions ou macros étant appelées par la bibliothèque obstack. L'une, obstack_chunk_alloc, est utilisée pour allouer les blocs de mémoire dans lesquels les objets sont regroupés. L'autre, obstack_chunk_free, est utilisée pour renvoyer les blocs lorsque les objets qu'ils contiennent sont libérés. Ces macros doivent apparaître avant toute utilisation d'obstacks dans le fichier source.

En général, elles sont définies pour utiliser malloc via l'intermédiaire xmalloc. Cela se fait avec la paire de définitions de macro suivante :

#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free

Bien que la mémoire obtenue en utilisant obstacks provienne en réalité de malloc, l'utilisation d'obstacks est plus rapide car malloc est appelé moins souvent, pour des blocs de mémoire plus volumineux.

Au moment de l'exécution, avant que le programme puisse utiliser un objet struct obstack comme obstack, il doit initialiser l'obstack en appelant obstack_init.

Fonction :

int obstack_init (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Safe mem

Initialisez obstack obstack-ptr pour l'allocation d'objets. Cette fonction appelle la fonction obstack_chunk_alloc de l'obstack. Si l'allocation de mémoire échoue, la fonction pointée par obstack_alloc_failed_handler est appelée. La fonction obstack_init renvoie toujours 1 (avis de compatibilité : les anciennes versions d'obstack renvoyaient 0 en cas d'échec de l'allocation).

Voici deux exemples de la manière d'allouer l'espace pour un obstack et de l'initialiser. Tout d'abord, un obstack étant une variable statique :

  1. static struct obstack myobstack;
  2. ...
  3. obstack_init (&myobstack);

Deuxièmement, un obstack étant lui-même alloué dynamiquement :

  1. struct obstack *myobstack_ptr  = (struct obstack *) xmalloc (sizeof (struct obstack));
  2.  
  3. obstack_init (myobstack_ptr);

Variable :

obstack_alloc_failed_handler

La valeur de cette variable est un pointeur vers une fonction qu'obstack utilise lorsque obstack_chunk_alloc ne parvient pas à allouer de la mémoire. L'action par défaut consiste à afficher un message et à abandonner. Vous devez fournir une fonction appelant exit ou longjmp et ne renvoyant rien.

  1. void my_obstack_alloc_failed (void)
  2. ...
  3. obstack_alloc_failed_handler = &my_obstack_alloc_failed;

Allocation dans un Obstack

La manière la plus directe d'allouer un objet dans un Obstack est d'utiliser obstack_alloc, étant appelé presque comme malloc.

Fonction :

void * obstack_alloc (struct obstack *obstack-ptr, int size)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

Cela alloue un bloc non initialisé de size octets dans un obstack et renvoie son adresse. Ici, obstack-ptr spécifie dans quel obstack allouer le bloc ; c'est l'adresse de l'objet struct obstack représentant l'obstack. Chaque fonction ou macro obstack nécessite que vous spécifiiez un obstack-ptr comme premier paramètre.

Cette fonction appelle la fonction obstack_chunk_alloc de l'obstack si elle doit allouer un nouveau bloc de mémoire; elle appelle obstack_alloc_failed_handler si l'allocation de mémoire par obstack_chunk_alloc a échoué.

Par exemple, voici une fonction allouant une copie d'une chaîne str dans un obstack spécifique, se trouvant dans la variable string_obstack :

  1. struct obstack string_obstack;
  2.  
  3. char * copystring (char *string) {
  4.  size_t len = strlen (string) + 1;
  5.  char *s = (char *) obstack_alloc (&string_obstack, len);
  6.  memcpy (s, string, len);
  7.  return s;
  8. }

Pour allouer un bloc avec un contenu spécifié, utilisez la fonction obstack_copy, déclarée comme ceci :

Fonction :

void * obstack_copy (struct obstack *obstack-ptr, void *address, int size)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

Cela alloue un bloc et l'initialise en copiant size octets de données à partir de l'adresse. Il appelle obstack_alloc_failed_handler si l'allocation de mémoire par obstack_chunk_alloc a échoué.

Fonction :

void * obstack_copy0 (struct obstack *obstack-ptr, void *address, int size)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

Comme obstack_copy, mais ajoute un octet supplémentaire contenant un caractère nul. Cet octet supplémentaire n'est pas compté dans la taille du paramètre.

La fonction obstack_copy0 est pratique pour copier une séquence de caractères dans un obstack sous forme de chaîne de caractères terminée par un caractère nul. Voici un exemple de son utilisation :

  1. char * obstack_savestring (char *addr, int size) {
  2.   return obstack_copy0 (&myobstack, addr, size);
  3. }

Comparez ceci avec l'exemple précédent de savestring utilisant malloc.

Libération d'objets dans un obstack

Pour libérer un objet alloué dans un obstack, utilisez la fonction obstack_free. Comme l'obstack est une pile d'objets, la libération d'un objet libère automatiquement tous les autres objets alloués plus récemment dans le même obstack.

Fonction :

void obstack_free (struct obstack *obstack-ptr, void *object)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt.

Si object est un pointeur nul, tout ce qui est alloué dans l'obstack est libéré. ??Sinon, object doit être l'adresse d'un objet alloué dans l'obstack. Ensuite, object est libéré, ainsi que tout ce qui est alloué dans obstack-ptr depuis object.

Notez que si object est un pointeur nul, le résultat est un obstack non initialisé. Pour libérer toute la mémoire d'un obstack mais la laisser valide pour une allocation ultérieure, appelez obstack_free avec l'adresse du premier objet alloué sur l'obstack :

  1. obstack_free (obstack_ptr, first_object_allocated_ptr);

Rappelons que les objets d'un obstack sont groupés en morceaux. Lorsque tous les objets d'un morceau deviennent libres, la bibliothèque obstack libère automatiquement le morceau. Ensuite, d'autres obstacks, ou une allocation non-obstack, peuvent réutiliser l'espace du morceau.

Fonctions et macros Obstack

Les interfaces permettant d'utiliser les obstacks peuvent être définies soit comme des fonctions, soit comme des macros, selon le compilateur. La fonction obstack fonctionne avec tous les compilateurs C, y compris le ISO C et le C traditionnel, mais vous devez prendre certaines précautions si vous envisagez d'utiliser d'autres compilateurs que GNU C.

Si vous utilisez un compilateur C non ISO à l'ancienne, toutes les «fonctions» obstack sont en fait définies uniquement comme des macros. Vous pouvez appeler ces macros comme des fonctions, mais vous ne pouvez pas les utiliser d'une autre manière (par exemple, vous ne pouvez pas prendre leur adresse).

L'appel des macros nécessite une précaution particulière : à savoir, le premier opérande (le pointeur obstack) ne doit contenir aucun effet secondaire, car il peut être calculé plus d'une fois. Par exemple, si vous écrivez ceci :

  1. obstack_alloc (get_obstack (), 4);

vous constaterez que get_obstack peut être appelé plusieurs fois. Si vous utilisez *obstack_list_ptr++ comme paramètre de pointeur obstack, vous obtiendrez des résultats très étranges puisque l'incrémentation peut se produire plusieurs fois.

En ISO C, chaque fonction possède à la fois une définition de macro et une définition de fonction. La définition de fonction est utilisée si vous prenez l'adresse de la fonction sans l'appeler. Un appel ordinaire utilise la définition de macro par défaut, mais vous pouvez demander la définition de fonction à la place en écrivant le nom de la fonction entre parenthèses, comme indiqué ici :

  1. char *x;
  2. void *(*funcp) ();
  3. /* Utilisez la macro. */
  4. x = (char *) obstack_alloc (obptr, size);
  5. /* Appelez la fonction. */
  6. x = (char *) (obstack_alloc) (obptr, size);
  7. /* Prenez l'adresse de la fonction. */
  8. funcp = obstack_alloc;

C'est la même situation existant dans ISO C pour les fonctions de la bibliothèque standard.

Avertissement : lorsque vous utilisez les macros, vous devez prendre la précaution d'éviter les effets de bord dans le premier opérande, même en ISO C.

Si vous utilisez le compilateur GNU C, cette précaution n'est pas nécessaire, car diverses extensions de langage dans GNU C permettent de définir les macros de manière à ne calculer chaque paramètre qu'une seule fois.

Faire grandir des objets

Étant donné que la mémoire dans les blocs obstack est utilisée de manière séquentielle, il est possible de construire un objet étape par étape, en ajoutant un ou plusieurs octets à la fois jusqu'à la fin de l'objet. Avec cette technique, vous n'avez pas besoin de savoir combien de données vous allez mettre dans l'objet jusqu'à ce que vous arriviez à la fin de celui-ci. Nous appelons cela la technique de croissance des objets. Les fonctions spéciales pour ajouter des données à l'objet en croissance sont décrites dans cette section.

Vous n'avez rien de spécial à faire lorsque vous commencez à faire grandir un objet. L'utilisation d'une des fonctions pour ajouter des données à l'objet le démarre automatiquement. Cependant, il est nécessaire d'indiquer explicitement quand l'objet est terminé. Cela se fait avec la fonction obstack_finish.

L'adresse réelle de l'objet ainsi construit n'est pas connue tant que l'objet n'est pas terminé. Jusqu'à ce moment-là, il reste toujours possible que vous ajoutiez tellement de données que l'objet doive être copié dans un nouveau bloc.

Pendant que l'obstack est utilisé pour un objet en croissance, vous ne pouvez pas l'utiliser pour l'allocation ordinaire d'un autre objet. Si vous essayez de le faire, l'espace déjà ajouté à l'objet en croissance fera partie de l'autre objet.

Fonction :

void obstack_blank (struct obstack *obstack-ptr, int size)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

La fonction la plus simple pour ajouter à un objet en croissance est obstack_blank, ajoutant de l'espace sans l'initialiser.

Fonction :

void obstack_grow (struct obstack *obstack-ptr, void *data, int size)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

Pour ajouter un bloc d'espace initialisé, utilisez obstack_grow, étant l'analogue d'obstack_copy pour l'objet en croissance. Il ajoute size octets de données à l'objet en croissance, en copiant le contenu à partir des données.

Fonction :

void obstack_grow0 (struct obstack *obstack-ptr, void *data, int size)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

Il s'agit de l'analogue d'obstack_copy0 pour l'objet en croissance. Il ajoute les octets de taille copiés à partir des données, suivis d'un caractère nul supplémentaire.

Fonction :

void obstack_1grow (struct obstack *obstack-ptr, char c)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

Pour ajouter un caractère à la fois, utilisez la fonction obstack_1grow. Elle ajoute un seul octet contenant c à l'objet en croissance.

Fonction :

void obstack_ptr_grow (struct obstack *obstack-ptr, void *data)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

Pour ajouter la valeur d'un pointeur, on peut utiliser la fonction obstack_ptr_grow. Elle ajoute sizeof (void *) octets contenant la valeur des données.

Fonction :

void obstack_int_grow (struct obstack *obstack-ptr, int data)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

Une seule valeur de type int peut être ajoutée à l'aide de la fonction obstack_int_grow. Elle ajoute sizeof (int) octets à l'objet en croissance et les initialise avec la valeur de data.

Fonction :

void * obstack_finish (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt

Lorsque vous avez fini de développer l'objet, utilisez la fonction obstack_finish pour le fermer et renvoyer son adresse finale.

Une fois l'objet terminé, l'obstack est disponible pour une allocation ordinaire ou pour développer un autre objet.

Cette fonction peut renvoyer un pointeur nul dans les mêmes conditions que obstack_alloc.

Lorsque vous construisez un objet en le développant, vous aurez probablement besoin de savoir par la suite quelle est sa longueur. Vous n'avez pas besoin de suivre cela au fur et à mesure que vous développez l'objet, car vous pouvez trouver la longueur de l'obstack juste avant de terminer l'objet avec la fonction obstack_object_size, déclarée comme suit :

Fonction :

int obstack_object_size (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Safe

Cette fonction renvoie la taille actuelle de l'objet en cours de croissance, en octets. N'oubliez pas d'appeler cette fonction avant de terminer l'objet. Une fois terminé, obstack_object_size renverra zéro.

Si vous avez commencé à faire grandir un objet et que vous souhaitez l'annuler, vous devez le terminer puis le libérer, comme ceci :

  1. obstack_free (obstack_ptr, obstack_finish (obstack_ptr));

Cela n'a aucun effet si aucun objet n'a grandi.

Vous pouvez utiliser obstack_blank avec un paramètre de taille négatif pour réduire la taille de l'objet actuel. N'essayez pas de le réduire au-delà de la longueur zéro, car vous ne savez pas ce qui se passera si vous faites cela.

Objets à croissance rapide supplémentaire

Les fonctions habituelles de croissance d'objets entraînent une surcharge pour vérifier s'il y a de la place pour la nouvelle croissance dans le bloc actuel. Si vous construisez fréquemment des objets par petites étapes de croissance, cette surcharge peut être importante.

Vous pouvez réduire la surcharge en utilisant des fonctions spéciales de «croissance rapide» faisant croître l'objet sans vérification. Pour avoir un programme robuste, vous devez effectuer la vérification vous-même. Si vous effectuez cette vérification de la manière la plus simple à chaque fois que vous êtes sur le point d'ajouter des données à l'objet, vous n'avez rien sauvegardé, car c'est ce que font les fonctions de croissance ordinaires. Mais si vous pouvez vous arranger pour vérifier moins souvent, ou vérifier plus efficacement, alors vous rendez le programme plus rapide.

La fonction obstack_room renvoie la quantité d'espace disponible dans le bloc actuel. Elle est déclarée comme suit :

Fonction :

int obstack_room (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Safe

Cela renvoie le nombre d'octets pouvant être ajoutés en toute sécurité à l'objet en croissance actuel (ou à un objet sur le point d'être démarré) dans obstack obstack-ptr à l'aide des fonctions de croissance rapide.

Tant que vous savez qu'il y a de la place, vous pouvez utiliser ces fonctions de croissance rapide pour ajouter des données à un objet en croissance :

Fonction :

void obstack_1grow_fast (struct obstack *obstack-ptr, char c)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Unsafe corrupt mem

La fonction obstack_1grow_fast ajoute un octet contenant le caractère c à l'objet en croissance dans obstack obstack-ptr.

Fonction :

void obstack_ptr_grow_fast (struct obstack *obstack-ptr, void *data)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Safe

La fonction obstack_ptr_grow_fast ajoute sizeof (void *) octets contenant la valeur de data à l'objet en croissance dans obstack obstack-ptr.

Fonction :

void obstack_int_grow_fast (struct obstack *obstack-ptr, int data)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Safe

La fonction obstack_int_grow_fast ajoute sizeof (int) octets contenant la valeur des données à l'objet en croissance dans obstack obstack-ptr.

Fonction :

void obstack_blank_fast (struct obstack *obstack-ptr, int size)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Safe

La fonction obstack_blank_fast ajoute size octets à l'objet en croissance dans obstack obstack-ptr sans les initialiser.

Lorsque vous vérifiez l'espace avec obstack_room et qu'il n'y a pas assez de place pour ce que vous voulez ajouter, les fonctions de croissance rapide ne sont pas sûres. Dans ce cas, utilisez simplement la fonction de croissance ordinaire correspondante à la place. Très bientôt, cela copiera l'objet dans un nouveau bloc; il y aura alors à nouveau beaucoup d'espace disponible.

Ainsi, chaque fois que vous utilisez une fonction de croissance ordinaire, vérifiez ensuite qu'il y a suffisamment d'espace à l'aide de obstack_room. Une fois l'objet copié dans un nouveau bloc, il y aura à nouveau beaucoup d'espace, donc le programme recommencera à utiliser les fonctions de croissance rapide.

Voici un exemple :

  1. void add_string (struct obstack *obstack, const char *ptr, int len) {
  2.  while (len > 0) {
  3.   int room = obstack_room (obstack);
  4.   if (room == 0) {
  5.    /* Pas assez de place. Ajoutez un caractère lentement, ce qui peut se copier dans un nouveau bloc et libérer de la place. */
  6.    obstack_1grow (obstack, *ptr++);
  7.    len--;
  8.   } else {
  9.    if (room > len) room = len;
  10.    /* Ajoutez rapidement autant que nous avons de la place. */
  11.    len -= room;
  12.    while (room-- > 0) obstack_1grow_fast (obstack, *ptr++);
  13.   }
  14.  }
  15. }

État d'un obstack

Voici des fonctions qui fournissent des informations sur l'état actuel de l'allocation dans un obstack. Vous pouvez les utiliser pour en savoir plus sur un objet tout en le faisant grandir.

Fonction :

void * obstack_base (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe | AS-Unsafe corrompu | AC-Safe

Cette fonction renvoie l'adresse provisoire du début de l'objet en cours de croissance dans obstack-ptr. Si vous terminez l'objet immédiatement, il aura cette adresse. Si vous l'agrandissez d'abord, il peut dépasser le bloc actuel - alors son adresse changera !

Si aucun objet ne grandit, cette valeur indique où le prochain objet que vous allouez commencera (en supposant encore une fois qu'il s'intègre dans le bloc actuel).

Fonction :

void * obstack_next_free (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe | AS-Unsafe corrupt | AC-Safe

Cette fonction renvoie l'adresse du premier octet libre dans le bloc actuel de obstack obstack-ptr. Il s'agit de la fin de l'objet en cours de croissance. Si aucun objet n'est en croissance, obstack_next_free renvoie la même valeur que obstack_base.

Fonction :

int obstack_object_size (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe race:obstack-ptr | AS-Safe | AC-Safe

Cette fonction renvoie la taille en octets de l'objet en cours de croissance. Cela équivaut à :

  1. obstack_next_free (obstack-ptr) - obstack_base (obstack-ptr)

Alignement des données dans les obstacks

Chaque obstack possède une limite d'alignement ; chaque objet alloué dans l'obstack démarre automatiquement sur une adresse étant un multiple de la limite spécifiée. Par défaut, cette limite est alignée de sorte que l'objet puisse contenir n'importe quel type de données.

Pour accéder à la limite d'alignement d'un obstack, utilisez la macro obstack_alignment_mask, dont le prototype de fonction ressemble à ceci :

Macro :

int obstack_alignment_mask (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

La valeur est un masque de bits; un bit qui est 1 indique que le bit correspondant dans l'adresse d'un objet doit être 0. La valeur du masque doit être inférieure à une puissance de 2 ; l'effet est que toutes les adresses d'objet sont des multiples de cette puissance de 2. La valeur par défaut du masque est une valeur permettant aux objets alignés de contenir tout type de données : par exemple, si sa valeur est 3, tout type de données peut être entreposé à des emplacements dont les adresses sont des multiples de 4. Une valeur de masque de 0 signifie qu'un objet peut démarrer sur n'importe quel multiple de 1 (c'est-à-dire qu'aucun alignement n'est requis).

L'extension de la macro obstack_alignment_mask est une lvalue, vous pouvez donc modifier le masque par affectation. Par exemple, cette instruction :

  1. obstack_alignment_mask (obstack_ptr) = 0;

a pour effet de désactiver le traitement d'alignement dans l'obstack spécifié.

Notez qu'un changement de masque d'alignement ne prend effet qu'après la prochaine allocation ou fin d'un objet dans l'obstack. Si vous ne développez pas un objet, vous pouvez faire en sorte que le nouveau masque d'alignement prenne effet immédiatement en appelant obstack_finish. Cela terminera un objet de longueur nulle, puis effectuera un alignement approprié pour l'objet suivant.

Morceaux Obstack

Les obstacks fonctionnent en allouant de l'espace pour eux-mêmes dans de gros morceaux, puis en répartissant l'espace dans les morceaux pour satisfaire vos demandes. Les morceaux ont normalement une longueur de 4096 octets, sauf si vous spécifiez une taille de morceau différente. La taille du morceau comprend 8 octets de surcharge n'étant pas réellement utilisés pour entreposer des objets. Quelle que soit la taille spécifiée, des morceaux plus longs seront alloués si nécessaire pour les objets longs.

La bibliothèque obstack alloue des morceaux en appelant la fonction obstack_chunk_alloc, que vous devez définir. Lorsqu'un morceau n'est plus nécessaire parce que vous avez libéré tous les objets qu'il contient, la bibliothèque obstack libère le morceau en appelant obstack_chunk_free, que vous devez également définir.

Ces deux éléments doivent être définis (en tant que macros) ou déclarés (en tant que fonctions) dans chaque fichier source qui utilise obstack_init. Le plus souvent, ils sont définis en tant que macros comme ceci :

#define obstack_chunk_alloc malloc
#define obstack_chunk_free free

Notez qu'il s'agit de macros simples (sans paramètres). Les définitions de macros avec des paramètres ne fonctionneront pas ! Il est nécessaire que obstack_chunk_alloc ou obstack_chunk_free, seul, se développe en un nom de fonction s'il n'est pas lui-même un nom de fonction.

Si vous allouez des blocs avec malloc, la taille du bloc doit être une puissance de 2. La taille du bloc par défaut, 4096, a été choisie car elle est suffisamment longue pour satisfaire de nombreuses requêtes typiques sur l'obstack mais suffisamment courte pour ne pas gaspiller trop de mémoire dans la partie du dernier bloc non encore utilisée.

Macro :

int obstack_chunk_size (struct obstack *obstack-ptr)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

Cela renvoie la taille de bloc de l'obstack donné.

Étant donné que cette macro se développe en une lvalue, vous pouvez spécifier une nouvelle taille de bloc en lui attribuant une nouvelle valeur. Cela n'affecte pas les blocs déjà alloués, mais modifiera la taille des blocs alloués pour cet obstack particulier à l'avenir. Il est peu probable qu'il soit utile de réduire la taille du bloc, mais l'augmenter peut améliorer l'efficacité si vous allouez de nombreux objets dont la taille est comparable à la taille du bloc. Voici comment procéder proprement :

  1. if (obstack_chunk_size (obstack_ptr) < new-chunk-size) obstack_chunk_size (obstack_ptr) = new-chunk-size;

Résumé des fonctions Obstack

Voici un résumé de toutes les fonctions associées aux obstacks. Chacune d'elles prend l'adresse d'un obstack (struct obstack *) comme premier paramètre.

void obstack_init (struct obstack *obstack-ptr)

Initialiser l'utilisation d'un obstack.

void *obstack_alloc (struct obstack *obstack-ptr, int size)

Allouer un objet de taille octets non initialisés.

void *obstack_copy (struct obstack *obstack-ptr, void *address, int size)

Allouer un objet de taille octets, avec le contenu copié depuis l'adresse.

void *obstack_copy0 (struct obstack *obstack-ptr, void *address, int size)

Allouer un objet de taille + 1 octet, dont la taille est copiée à partir de l'adresse, suivie d'un caractère nul à la fin.

void obstack_free (struct obstack *obstack-ptr, void *object)

Libérer l'objet (et tout ce qui a été alloué dans l'Obstack spécifié plus récemment que l'objet).

void obstack_blank (struct obstack *obstack-ptr, int size)

Ajoutez des octets non initialisés de taille à un objet en croissance.

void obstack_grow (struct obstack *obstack-ptr, void *address, int size)

Ajoutez des octets de taille, copiés à partir de l'adresse, à un objet en croissance.

void obstack_grow0 (struct obstack *obstack-ptr, void *address, int size)

Ajoutez des octets de taille, copiés à partir de l'adresse, à un objet en croissance, puis ajoutez un autre octet contenant un caractère nul.

void obstack_1grow (struct obstack *obstack-ptr, char data-char)

Ajoutez un octet contenant data-char à un objet en croissance.

void *obstack_finish (struct obstack *obstack-ptr)

Finalisez l'objet en cours de croissance et renvoyez son adresse permanente.

int obstack_object_size (struct obstack *obstack-ptr)

Obtenir la taille actuelle de l'objet en cours de croissance.

void obstack_blank_fast (struct obstack *obstack-ptr, int size)

Ajoutez des octets non initialisés de taille à un objet en croissance sans vérifier qu'il y a suffisamment de place.

void obstack_1grow_fast (struct obstack *obstack-ptr, char data-char)

Ajoutez un octet contenant data-char à un objet en croissance sans vérifier qu'il y a suffisamment de place.

int obstack_room (struct obstack *obstack-ptr)

Obtenez la quantité d'espace actuellement disponible pour la croissance de l'objet actuel.

int obstack_alignment_mask (struct obstack *obstack-ptr)

Le masque utilisé pour aligner le début d'un objet. Il s'agit d'une lvalue.

int obstack_chunk_size (struct obstack *obstack-ptr)

La taille d'allocation des blocs. Il s'agit d'une lvalue.

void *obstack_base (struct obstack *obstack-ptr)

Adresse de départ provisoire de l'objet en cours de croissance.

void *obstack_next_free (struct obstack *obstack-ptr)

Adresse située juste après la fin de l'objet en cours de croissance.

Entreposage automatique avec taille variable

La fonction alloca prend en charge une sorte d'allocation semi-dynamique dans laquelle les blocs sont alloués dynamiquement mais libérés automatiquement.

L'allocation d'un bloc avec alloca est une action explicite; vous pouvez allouer autant de blocs que vous le souhaitez et calculer la taille au moment de l'exécution. Mais tous les blocs sont libérés lorsque vous quittez la fonction à partir de laquelle alloca a été appelé, comme s'il s'agissait de variables automatiques déclarées dans cette fonction. Il n'existe aucun moyen de libérer l'espace explicitement.

Le prototype d'alloca se trouve dans stdlib.h. Cette fonction est une extension BSD.

Fonction :

void * alloca (size_t size)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

La valeur de retour de alloca est l'adresse d'un bloc de taille octets de mémoire, alloué dans le cadre de pile de la fonction appelante.

N'utilisez pas alloca à l'intérieur des arguments d'un appel de fonction : vous obtiendrez des résultats imprévisibles, car l'espace de pile pour alloca apparaîtrait sur la pile au milieu de l'espace pour les paramètres de la fonction. Un exemple de ce qu'il faut éviter est foo (x, alloca (4), y) :

Exemple d'alloca

A titre d'exemple d'utilisation d'alloca, voici une fonction qui ouvre un nom de fichier obtenu à partir de la concaténation de deux chaînes de paramètres et renvoie un descripteur de fichier ou moins un signifiant un échec :

  1. int open2 (char *str1, char *str2, int flags, int mode) {
  2.  char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
  3.  stpcpy (stpcpy (name, str1), str2);
  4.  return open (name, flags, mode);
  5. }

Voici comment vous obtiendrez les mêmes résultats avec malloc et free :

  1. int open2 (char *str1, char *str2, int flags, int mode) {
  2.  char *name = malloc (strlen (str1) + strlen (str2) + 1);
  3.  int desc;
  4.  if (name == 0) fatal ("Mémoire virtuelle dépassée");
  5.  stpcpy (stpcpy (name, str1), str2);
  6.  desc = open (name, flags, mode);
  7.  free (name);
  8.  return desc;
  9. }

Comme vous pouvez le constater, c'est plus simple avec alloca. Mais alloca a d'autres avantages plus importants, et quelques inconvénients.

Avantages d'alloca

Voici les raisons pour lesquelles alloca peut être préférable à malloc :

Pour illustrer cela, supposons que vous ayez une fonction open_or_report_error renvoyant un descripteur, comme open, si elle réussit, mais ne retourne pas à son appelant si elle échoue. Si le fichier ne peut pas être ouvert, il affiche un message d'erreur et saute au niveau de commande de votre programme en utilisant longjmp. Modifions open2 (voir l'exemple d'alloca) pour utiliser cette sous-routine :

  1. int open2 (char *str1, char *str2, int flags, int mode) {
  2.  char *name = (char *) alloca (strlen (str1) + strlen (str2) + 1);
  3.  stpcpy (stpcpy (name, str1), str2);
  4.  return open_or_report_error (name, flags, mode);
  5. }

En raison du fonctionnement d'alloca, la mémoire qu'il alloue est libérée même lorsqu'une erreur se produit, sans aucun effort particulier requis.

En revanche, la définition précédente d'open2 (utilisant malloc et free) développerait une fuite de mémoire si elle était modifiée de cette manière. Même si vous êtes prêt à effectuer d'autres modifications pour résoudre le problème, il n'existe pas de moyen simple de le faire.

Inconvénients d'alloca

Voici les inconvénients d'alloca par rapport à malloc :

Tableaux de taille variable GNU C

Dans GNU C, vous pouvez remplacer la plupart des utilisations de alloca par un tableau de taille variable. Voici à quoi ressemblerait alors open2 :

  1. int open2 (char *str1, char *str2, int flags, int mode) {
  2.  char name[strlen (str1) + strlen (str2) + 1];
  3.  stpcpy (stpcpy (name, str1), str2);
  4.  return open (name, flags, mode);
  5. }

Mais alloca n'est pas toujours équivalent à un tableau de taille variable, pour plusieurs raisons :

NB : si vous mélangez l'utilisation d'alloca et de tableaux de taille variable dans une même fonction, la sortie d'une portée dans laquelle un tableau de taille variable a été déclaré libère tous les blocs alloués avec alloca pendant l'exécution de cette portée.

Redimensionnement du segment de données

Les symboles de cette section sont déclarés dans unistd.h.

Vous n'utiliserez normalement pas les fonctions de cette section, car les fonctions décrites dans allocation d'entreposage pour les données de programme sont plus faciles à utiliser. Ce sont des interfaces vers un allocateur de mémoire de la bibliothèque glibc utilisant les fonctions ci-dessous. Les fonctions ci-dessous sont des interfaces simples pour les appels système.

Fonction :

int brk (void *addr)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

brk définit l'extrémité supérieure du segment de données du processus appelant sur addr.

L'adresse de la fin d'un segment est définie comme étant l'adresse du dernier octet du segment plus 1.

La fonction n'a aucun effet si addr est inférieur à l'extrémité inférieure du segment de données. (Ceci est considéré comme un succès, soit dit en passant.)

La fonction échoue si elle provoque le chevauchement du segment de données avec un autre segment ou dépasse la limite d'entreposage de données du processus.

La fonction est nommée d'après un cas historique courant où l'entreposage de données et la pile se trouvent dans le même segment. L'allocation d'entreposage de données augmente vers le haut à partir du bas du segment tandis que la pile augmente vers le bas à partir du haut du segment et le rideau entre eux est appelé le break.

La valeur de retour est zéro en cas de succès. En cas d'échec, la valeur de retour est -1 et errno est défini en conséquence. Les valeurs errno suivantes sont spécifiques à cette fonction :

Constante Description
ENOMEM La requête entraînerait le chevauchement du segment de données avec un autre segment ou le dépassement de la limite de stockage des données du processus.

Fonction :

void *sbrk (ptrdiff_t delta)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

Cette fonction est identique à brk, sauf que vous spécifiez la nouvelle extrémité du segment de données comme un delta de déplacement par rapport à l'extrémité actuelle et, en cas de succès, la valeur de retour est l'adresse de l'extrémité résultante du segment de données au lieu de zéro.

Cela signifie que vous pouvez utiliser «sbrk(0)» pour découvrir quelle est l'extrémité actuelle du segment de données.

Protection de la mémoire

Lorsqu'une page est cartographiée à l'aide de mmap, les drapeaux de protection de page peuvent être spécifiés à l'aide de le paramètre protection flags.

Les drapeaux suivants sont disponibles :

Constante Description
PROT_WRITE La mémoire peut être écrite.
PROT_READ La mémoire peut être lue. Sur certaines architectures, ce drapeau implique que la mémoire peut également être exécutée (comme si PROT_EXEC avait été spécifié en même temps).
PROT_EXEC La mémoire peut être utilisée pour stocker des instructions pouvant ensuite être exécutées. Sur la plupart des architectures, ce drapeau implique que la mémoire peut être lue (comme si PROT_READ avait été spécifié).
PROT_NONE Ce drapeau doit être spécifié seul. La mémoire est réservée, mais ne peut pas être lue, écrite ou exécutée. Si ce drapeau est spécifié dans un appel à mmap, une zone de mémoire virtuelle sera réservée pour une utilisation ultérieure dans le processus, et les appels mmap sans le drapeau MAP_FIXED ne l'utiliseront pas pour les allocations ultérieures. Pour les cartographies anonymes, le noyau ne réservera aucune mémoire physique pour l'allocation au moment de la création de la cartographie.

Le système d'exploitation peut conserver une trace de ces drapeaux séparément même si le matériel sous-jacent les traite de la même manière à des fins de contrôle d'accès (comme cela se produit avec PROT_READ et PROT_EXEC sur certaines plates-formes). Sur les systèmes GNU, PROT_EXEC implique toujours PROT_READ, de sorte que les utilisateurs peuvent visualiser le code machine s'exécutant sur leur système.

Un accès inapproprié provoquera une erreur de segmentation.

Après l'allocation, les drapeaux de protection peuvent être modifiés à l'aide de la fonction mprotect.

Fonction :

int mprotect (void *address, size_t length, int protection)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

Un appel réussi à la fonction mprotect modifie les drapeaux de protection d'au moins length octets de mémoire, en commençant à address.

address doit être alignée sur la taille de page pour la cartographie. La taille de page système peut être obtenue en appelant sysconf avec le paramètre _SC_PAGESIZE. La taille de page système est la granularité dans laquelle la protection de page des cartographies de mémoire anonymes et de la plupart des cartographies de fichiers peut être modifiée. La mémoire cartographiée à partir de fichiers ou de périphériques spéciaux peut avoir une granularité de page plus grande que la taille de page système et peut nécessiter un alignement plus important.

length est le nombre d'octets dont les drapeaux de protection doivent être modifiés. Il est automatiquement arrondi au multiple supérieur de la taille de page système.

protection est une combinaison des drapeaux PROT_* décrits ci-dessus.

La fonction mprotect renvoie 0 en cas de succès et -1 en cas d'échec.

Les conditions d'erreur errno suivantes sont définies pour cette fonction :

Constante Description
ENOMEM Le système n'a pas pu allouer de ressources pour répondre à la demande. Cela peut se produire si la mémoire physique du système n'est pas suffisante pour l'allocation d'entreposage de sauvegarde. L'erreur peut également se produire si les nouveaux drapeaux de protection provoquent la séparation de la région mémoire de ses voisines et que la limite du processus pour le nombre de ces régions mémoire distinctes est dépassée.
EINVAL L'adresse n'est pas correctement alignée sur une limite de page pour le cartographie, ou la longueur (après avoir arrondi à la taille de page système) n'est pas un multiple de la taille de page applicable pour la cartographie, ou la combinaison de drapeaux dans la protection n'est pas valide.
EACCES Le fichier pour une cartographie basé sur des fichiers n'a pas été ouvert avec des drapeaux ouverts compatibles avec la protection.
EPERM La politique de sécurité du système n'autorise pas de cartographier avec les drapeaux spécifiés. Par exemple, les cartographies étant à la fois PROT_EXEC et PROT_WRITE peuvent ne pas être autorisés.

Si la fonction mprotect est utilisée pour rendre une région de mémoire inaccessible en spécifiant le drapeau de protection PROT_NONE et que l'accès est restauré ultérieurement, la mémoire conserve son contenu précédent.

Sur certains systèmes, il peut ne pas être possible de spécifier des drapeaux supplémentaires n'étant pas présents lors de la création initiale de la cartographie. Par exemple, une tentative de rendre une région de mémoire exécutable pourrait échouer si les drapeaux de protection initiaux étaient «PROT_READ | PROT_WRITE».

En général, la fonction mprotect peut être utilisée pour modifier la mémoire de n'importe quel processus, quelle que soit la manière dont elle a été allouée. Cependant, l'utilisation portable de la fonction nécessite qu'elle ne soit utilisée qu'avec des régions de mémoire renvoyées par mmap ou mmap64.

Les clefs de protection de la mémoire

Sur certains systèmes, des restrictions supplémentaires peuvent être ajoutées à des pages spécifiques à l'aide de clefs de protection de la mémoire. Ces restrictions fonctionnent comme suit :

Les nouveaux processus léger et sous-processus héritent des droits d'accès du processus léger actuel. Si une clef de protection est allouée ultérieurement, les processus léger existants (à l'exception du processus léger actuel) utiliseront une valeur par défaut système non spécifiée pour les droits d'accès associés aux clefs nouvellement allouées.

Lors de l'entrée dans un gestionnaire de signal, le système réinitialise les droits d'accès du processus léger actuel afin que les pages avec la clef par défaut soient accessibles, mais les droits d'accès pour les autres clefs de protection ne sont pas spécifiés.

Les applications doivent allouer une clef une fois à l'aide de pkey_alloc et appliquer la clef aux régions de mémoire nécessitant une protection spéciale avec pkey_mprotect :

  1. int key = pkey_alloc (0, PKEY_DISABLE_ACCESS);
  2. if (key < 0)
  3.  /* Effectuer la vérification des erreurs, y compris le repli en cas de manque de support. */
  4.  ...;
  5.  
  6. /* Appliquez la clef à une région de mémoire spéciale utilisée pour entreposer des données critiques. */
  7. if (pkey_mprotect (region, region_length, PROT_READ | PROT_WRITE, key) < 0)
  8.  ...; /* Effectuer une vérification des erreurs (généralement fatale).  */

Si l'allocation de clef échoue en raison d'un manque de prise en charge des clés de protection de la mémoire, l'appel à pkey_mprotect peut généralement être ignoré. Dans ce cas, la région ne sera pas protégée par défaut. Il est également possible d'appeler pkey_mprotect avec une valeur de clef de -1, auquel cas il se comportera de la même manière que mprotect.

Après l'affectation de l'allocation de clef aux pages de mémoire, pkey_set peut être utilisé pour acquérir temporairement l'accès à la région de mémoire et l'abandonner à nouveau :

  1. if (key >= 0 && pkey_set (key, 0) < 0)
  2.  ...; /* Effectuer une vérification des erreurs (généralement fatale). */
  3. /* À ce stade, le processus léger actuel dispose d'un accès en lecture-écriture à la région mémoire. */
  4. ...
  5. /* Révoquer à nouveau l'accès. */
  6. if (key >= 0 && pkey_set (key, PKEY_DISABLE_ACCESS) < 0)
  7.  ...; /* Effectuer une vérification des erreurs (généralement fatale). */

Dans cet exemple, une valeur de clef négative indique qu'aucune clef n'a été allouée, ce qui signifie que le système ne prend pas en charge les clefs de protection de la mémoire et qu'il n'est pas nécessaire de modifier les droits d'accès du processus léger actuel (car il y a toujours accès).

Par rapport à l'utilisation de mprotect pour modifier les drapeaux de protection de page, cette approche présente deux avantages : elle est thread-safe dans le sens où les droits d'accès ne sont modifiés que pour le processus léger actuel, donc un autre processus léger modifiant ses propres droits d'accès en même temps pour accéder au cartographie ne verra pas soudainement ses droits d'accès révoqués. Et pkey_set n'implique généralement pas d'appel au noyau et de changement de contexte, il est donc plus efficace.

Fonction :

int pkey_alloc (unsigned int flags, unsigned int restrictions)

Préliminaire : | MT-Safe | AS-Safe | AC-Unsafe corrompu

Allouer une nouvelle clef de protection. Le paramètre flags est réservé et doit être nul. Le paramètre restrictions spécifie les droits d'accès étant appliqués au processus léger actuel (comme avec pkey_set ci-dessous). Les droits d'accès des autres processus léger ne sont pas modifiés.

La fonction renvoie la nouvelle clef de protection, un nombre non négatif ou -1 en cas d'erreur.

Les conditions d'erreur errno suivantes sont définies pour cette fonction :

Constante Description
ENOSYS Le système n'implémente pas de clefs de protection de mémoire.
EINVAL Le drapeau flags n'est pas nul. Le paramètre restrictions n'est pas valide. Le système n'implémente pas les clefs de protection de la mémoire ou fonctionne dans un mode dans lequel les clefs de protection de la mémoire sont désactivées.
ENOSPC Toutes les clefs de protection disponibles ont déjà été attribuées. Le système n'implémente pas de clefs de protection de mémoire ou fonctionne dans un mode dans lequel les clefs de protection de mémoire sont désactivées.

Fonction :

int pkey_free (int key)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

Libérez la clef de protection afin qu'elle puisse être réutilisée par pkey_alloc.

L'appel de cette fonction ne modifie pas les droits d'accès de la clef de protection libérée. Le processus léger appelant et les autres processus léger peuvent conserver l'accès à celle-ci, même si elle est à nouveau allouée ultérieurement. Pour cette raison, il n'est pas recommandé d'appeler la fonction pkey_free.

Constante Description
ENOSYS Le système n'implémente pas de clefs de protection de mémoire.
EINVAL Le paramètre key n'est pas une clef de protection valide.

Fonction :

int pkey_mprotect (void *address, size_t length, int protection, int key)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

Similaire à mprotect, mais définit également la clef de protection de la mémoire pour la région de mémoire sur key.

Certains systèmes utilisent des clefs de protection de la mémoire pour émuler certaines combinaisons d'indicateurs de protection. Dans de telles circonstances, la spécification d'une clef de protection explicite peut se comporter comme si des drapeaux supplémentaires avaient été spécifiés dans protection, même si cela ne se produit pas avec la clef de protection par défaut. Par exemple, certains systèmes peuvent prendre en charge les cartographies PROT_EXEC uniquement avec une clef de protection par défaut, et la mémoire avec une clef ayant été allouée à l'aide de pkey_alloc sera toujours lisible si PROT_EXEC est spécifié sans PROT_READ.

Si key est -1, la clef de protection par défaut est appliquée à la cartographie, comme si mprotect avait été appelé.

La fonction pkey_mprotect renvoie 0 en cas de succès et -1 en cas d'échec. Les mêmes conditions d'erreur errno que pour mprotect sont définies pour cette fonction, avec l'ajout suivant :

Constante Description
EINVAL Le paramètre key n'est pas -1 ou une clef de protection de mémoire valide allouée à l'aide de pkey_alloc.
ENOSYS Le système n'implémente pas de clefs de protection de mémoire et la clef n'est pas -1.

Fonction :

int pkey_set (int key, unsigned int rights)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

Modifiez les droits d'accès du processus léger actuel pour les pages mémoire avec la clef de protection key à rights. Si rights est égal à zéro, aucune restriction d'accès supplémentaire en plus des drapeaux de protection de page n'est appliquée. Sinon, rights est une combinaison des drapeaux suivants :

Constante Description
PKEY_DISABLE_WRITE Les tentatives ultérieures d'écriture dans la mémoire avec la clef de protection spécifiée échoueront.
PKEY_DISABLE_ACCESS Les tentatives ultérieures d'écriture ou de lecture dans la mémoire avec la clef de protection spécifiée échoueront.

Les opérations non spécifiées comme drapeaux ne sont pas restreintes. En particulier, cela signifie que la région de mémoire restera exécutable si elle a été cartographiée avec le drapeau de protection PROT_EXEC et que PKEY_DISABLE_ACCESS a été spécifié.

L'appel de la fonction pkey_set avec une clef de protection n'ayant pas été allouée par pkey_alloc entraîne un comportement indéfini. Cela signifie que l'appel de cette fonction sur des systèmes ne prenant pas en charge les clefs de protection de la mémoire est indéfini.

La fonction pkey_set renvoie 0 en cas de réussite et -1 en cas d'échec.

Les conditions d'erreur errno suivantes sont définies pour cette fonction :

Constante Description
EINVAL Le système ne prend pas en charge les restrictions de droits d'accès exprimées dans le paramètre rights.

Fonction :

int pkey_get (int key)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

Renvoie les droits d'accès du processus actuel pour les pages mémoire avec la clef de protection key. La valeur de retour est zéro ou une combinaison des drapeaux PKEY_DISABLE_*.

L'appel de la fonction pkey_get avec une clef de protection n'ayant pas été allouée par pkey_alloc entraîne un comportement indéfini. Cela signifie que l'appel de cette fonction sur des systèmes ne prenant pas en charge les clefs de protection de la mémoire est indéfini.

Verrouillage des pages

Vous pouvez demander au système d'associer une page de mémoire virtuelle particulière à un cadre de page réel et de la conserver ainsi, c'est-à-dire de faire en sorte que la page soit paginée si elle ne l'est pas déjà et de la marquer de manière à ce qu'elle ne soit jamais paginée et ne provoque donc jamais d'erreur de page. C'est ce qu'on appelle le verrouillage d'une page.

Les fonctions de cette page verrouillent et déverrouillent les pages du processus appelant.

Pourquoi verrouiller les pages

Étant donné que les défauts de page provoquent la pagination transparente des pages paginées, un processus n'a que rarement besoin de se soucier du verrouillage des pages. Cependant, il y a deux raisons pour lesquelles les gens le font parfois :

Sachez que lorsque vous verrouillez une page, cela représente une image de page en moins pouvant être utilisée pour sauvegarder une autre mémoire virtuelle (par le même processus ou par d'autres), ce qui peut signifier plus de défauts de page, ce qui signifie que le système fonctionne plus lentement. En fait, si vous verrouillez suffisamment de mémoire, certains programmes peuvent ne pas pouvoir s'exécuter du tout par manque de mémoire réelle.

Détails de la mémoire verrouillée

Un verrou de mémoire est associé à une page virtuelle, pas à un cadre réel. La règle de pagination est la suivante : si un cadre a au moins une page verrouillée en arrière, ne le paginez pas.

Les verrous de mémoire ne s'empilent pas. Autrement dit, vous ne pouvez pas verrouiller une page particulière deux fois, de sorte qu'elle doive être déverrouillée deux fois avant d'être réellement déverrouillée. Soit elle est verrouillée, soit elle ne l'est pas.

Un verrou de mémoire persiste jusqu'à ce que le processus propriétaire de la mémoire le déverrouille explicitement. (Mais la terminaison du processus et exec provoquent la cessation de l'existence de la mémoire virtuelle, ce qui signifie qu'elle n'est plus verrouillée).

Les verrous de mémoire ne sont pas hérités par les processus enfants. (Mais notez que sur un système Unix moderne, immédiatement après un fork, l'espace d'adressage virtuel du parent et de l'enfant sont soutenus par les mêmes cadres de page réels, de sorte que l'enfant bénéficie des verrous du parent).

En raison de sa capacité à impacter d'autres processus, seul le superutilisateur peut verrouiller une page. Tout processus peut déverrouiller sa propre page.

Le système définit des limites sur la quantité de mémoire qu'un processus peut verrouiller et sur la quantité de mémoire réelle qu'il peut lui dédier.

Sous Linux, les pages verrouillées ne sont pas aussi verrouillées que vous pourriez le penser. Deux pages virtuelles n'étant pas de la mémoire partagée peuvent néanmoins être sauvegardées par la même trame réelle. Le noyau fait cela au nom de l'efficacité lorsqu'il sait que les deux pages virtuelles contiennent des données identiques, et le fait même si l'une ou les deux pages virtuelles sont verrouillées.

Mais lorsqu'un processus modifie l'une de ces pages, le noyau doit lui obtenir une trame distincte et la remplir avec les données de la page. C'est ce qu'on appelle une erreur de page de copie sur écriture. Cela prend un peu de temps et dans un cas pathologique, l'obtention de cette trame peut nécessiter des entrées/sorties.

Pour vous assurer que cela n'arrive pas à votre programme, ne verrouillez pas simplement les pages. Écrivez-y également, à moins que vous ne sachiez que vous n'y écrirez jamais. Et pour vous assurer que vous disposez de trames pré-allouées pour votre pile, entrez une portée qui déclare une variable automatique C plus grande que la taille de pile maximale dont vous aurez besoin, définissez-la sur quelque chose, puis revenez de sa portée.

Fonctions pour verrouiller et déverrouiller des pages

Les symboles de cette section sont déclarés dans sys/mman.h. Ces fonctions sont définies par POSIX.1b, mais leur disponibilité dépend de votre noyau. Si votre noyau n'autorise pas ces fonctions, elles existent mais échouent toujours. Elles sont disponibles avec un noyau Linux.

Note de portabilité : POSIX.1b exige que lorsque les fonctions mlock et munlock sont disponibles, le fichier unistd.h définisse la macro _POSIX_MEMLOCK_RANGE et le fichier boundaries.h définit la macro PAGESIZE comme étant la taille d'une page mémoire en octets. Il exige que lorsque les fonctions mlockall et munlockall sont disponibles, le fichier unistd.h définisse la macro _POSIX_MEMLOCK. La bibliothèque glibc est conforme à cette exigence.

Fonction :

int mlock (const void *addr, size_t len)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

mlock verrouille un intervalle de pages virtuelles du processus appelant.

L'intervalle de mémoire commence à l'adresse addr et mesure len octets. En fait, comme vous devez verrouiller des pages entières, il s'agit de l'intervalle de pages incluant une partie de l'intervalle spécifiée.

Lorsque la fonction retourne avec succès, chacune de ces pages est sauvegardée par (connectée à) une trame réelle (est résidente) et est marquée pour rester ainsi. Cela signifie que la fonction peut provoquer des insertions de page et devoir les attendre.

Lorsque la fonction échoue, elle n'affecte pas l'état de verrouillage des pages.

La valeur de retour est zéro si la fonction réussit. Sinon, elle est -1 et errno est défini en conséquence. Les valeurs errno spécifiques à cette fonction sont :

Constante Description
ENOMEM Au moins une partie de l'intervalle d'adresses spécifiée n'existe pas dans l'espace d'adressage virtuel du processus appelant. Le verrouillage entraînerait le dépassement de la limite de pages verrouillées du processus.
EPERM Le processus d'appel n'est pas superutilisateur.
EINVAL len n'est pas positif.
ENOSYS Le noyau ne fournit pas de capacité mlock.

Fonction :

int mlock2 (const void *addr, size_t len, unsigned int flags)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

Cette fonction est similaire à mlock. Si flags est nul, un appel à mlock2 se comporte exactement comme l'appel équivalent à mlock.

Le paramètre flags doit être une combinaison de zéro ou plusieurs des flags suivants :

Constante Description
MLOCK_ONFAULT Seules les pages de l'intervalle d'adresses spécifiée étant déjà en mémoire sont immédiatement verrouillées. Les pages supplémentaires de la plage sont automatiquement verrouillées en cas d'erreur de page et d'allocation de mémoire.

Comme mlock, mlock2 renvoie zéro en cas de succès et -1 en cas d'échec, en définissant errno en conséquence. Les valeurs errno supplémentaires définies pour mlock2 sont :

Constante Description
EINVAL Le paramètre falgs spécifié (non nul) n'est pas pris en charge par ce système.

Vous pouvez verrouiller toute la mémoire d'un processus avec mlockall. Vous déverrouillez la mémoire avec munlock ou munlockall.

Pour éviter tous les défauts de page dans un programme C, vous devez utiliser mlockall, car une partie de la mémoire utilisée par un programme est cachée au code C, par exemple la pile et les variables automatiques, et vous ne sauriez pas quelle adresse indiquer à mlock.

Fonction :

int munlock (const void *addr, size_t len)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

munlock déverrouille un intervalle de pages virtuelles du processus appelant.

munlock est l'inverse de mlock et fonctionne de manière totalement analogue à mlock, sauf qu'il n'y a pas d'échec EPERM.

Fonction :

int mlockall (int flags)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe

mlockall verrouille toutes les pages de l'espace d'adressage de la mémoire virtuelle d'un processus et/ou toutes celles y étant ajoutées ultérieurement. Cela inclut les pages du code, les données et le segment de pile, ainsi que les bibliothèques partagées, les données du noyau de l'espace utilisateur, la mémoire partagée et les fichiers cartographiés en mémoire.

flags est une chaîne de drapeaux à un seul bit représentée par les macros suivantes. Ils indiquent à mlockall laquelle de ses fonctions vous voulez. Tous les autres bits doivent être à zéro.

Constante Description
MCL_CURRENT Verrouiller toutes les pages existant actuellement dans l'espace d'adressage virtuel du processus appelant.
MCL_FUTURE Définissez un mode de sorte que toutes les pages ajoutées à l'espace d'adressage virtuel du processus à l'avenir soient verrouillées dès leur création. Ce mode n'affecte pas les futurs espaces d'adressage appartenant au même processus, donc exec, remplaçant l'espace d'adressage d'un processus, efface MCL_FUTURE.

Lorsque la fonction est exécutée avec succès et que vous avez spécifié MCL_CURRENT, toutes les pages du processus sont sauvegardées par des trames réelles (elles sont résidentes) et sont marquées pour rester ainsi. Cela signifie que la fonction peut provoquer des insertions de page et devoir les attendre.

Lorsque le processus est en mode MCL_FUTURE parce qu'il a exécuté avec succès cette fonction et spécifié MCL_CURRENT, tout appel système par le processus nécessitant l'ajout d'espace à son espace d'adressage virtuel échoue avec errno = ENOMEM si le verrouillage de l'espace supplémentaire entraînerait le processus à dépasser sa limite de pages verrouillées. Dans le cas où l'ajout d'espace d'adressage ne pouvant pas être pris en charge est l'extension de la pile, l'extension de la pile échoue et le noyau envoie un signal SIGSEGV au processus.

Lorsque la fonction échoue, elle n'affecte pas l'état de verrouillage des pages ou le futur mode de verrouillage.

La valeur de retour est zéro si la fonction réussit. Sinon, elle est -1 et errno est défini en conséquence. Les valeurs errno spécifiques à cette fonction sont :

Constante Description
ENOMEM Au moins une partie de l'intervalle d'adresses spécifiée n'existe pas dans l'espace d'adressage virtuel du processus appelant. Le verrouillage entraînerait le dépassement de la limite de pages verrouillées du processus.
EPERM Le processus d'appel n'est pas superutilisateur.
EINVAL Les bits non définis dans les indicateurs ne sont pas nuls.
ENOSYS Le noyau ne fournit pas la capacité mlockall.

Vous pouvez verrouiller uniquement des pages spécifiques avec mlock. Vous pouvez déverrouiller des pages avec munlockall et munlock.

Fonction :

int munlockall (void)

Préliminaire : | MT-Safe | AS-Safe | AC-Safe.

munlockall déverrouille chaque page dans l'espace d'adressage virtuel du processus appelant et désactive le mode de verrouillage futur MCL_FUTURE.

La valeur de retour est zéro si la fonction réussit. Sinon, elle est -1 et errno est défini en conséquence. La seule façon dont cette fonction peut échouer est pour des raisons génériques que toutes les fonctions et tous les appels système peuvent échouer, il n'y a donc pas de valeurs errno spécifiques.



PARTAGER CETTE PAGE SUR
Dernière mise à jour : Mercredi, le 1er janvier 2025