C Language
Geheugen management
Zoeken…
Invoering
Voor het beheer van dynamisch toegewezen geheugen biedt de standaard C-bibliotheek de functies malloc()
, calloc()
, realloc()
en free()
. In C99 en later is er ook aligned_alloc()
. Sommige systemen bieden ook alloca()
.
Syntaxis
- void * align_alloc (size_t alignment, size_t size); / * Alleen sinds C11 * /
- void * calloc (size_t nelements, size_t size);
- void free (void * ptr);
- void * malloc (size_t size);
- void * realloc (void * ptr, size_t size);
- void * alloca (size_t size); / * van alloca.h, niet standaard, niet draagbaar, gevaarlijk. * /
parameters
naam | Beschrijving |
---|---|
grootte ( malloc , realloc en aligned_alloc ) | totale geheugengrootte in bytes. Voor aligned_alloc de grootte een integraal veelvoud van uitlijning zijn. |
maat ( calloc ) | grootte van elk element |
Nelements | aantal elementen |
ptr | pointer naar toegewezen geheugen eerder geretourneerd door malloc , calloc , realloc of aligned_alloc |
opstelling | uitlijning van toegewezen geheugen |
Opmerkingen
Merk op dat aligned_alloc()
alleen is gedefinieerd voor C11 of hoger.
Systemen zoals die gebaseerd op POSIX bieden andere manieren om uitgelijnd geheugen toe te wijzen (bijv. posix_memalign()
), en hebben ook andere opties voor geheugenbeheer (bijv. posix_memalign()
mmap()
).
Geheugen vrijmaken
Het is mogelijk om dynamisch toegewezen geheugen vrij te geven door free () aan te roepen.
int *p = malloc(10 * sizeof *p); /* allocation of memory */
if (p == NULL)
{
perror("malloc failed");
return -1;
}
free(p); /* release of memory */
/* note that after free(p), even using the *value* of the pointer p
has undefined behavior, until a new value is stored into it. */
/* reusing/re-purposing the pointer itself */
int i = 42;
p = &i; /* This is valid, has defined behaviour */
Het geheugen waarnaar p
verwijst, wordt teruggehaald (hetzij door de libc-implementatie of door het onderliggende besturingssysteem) na de aanroep naar free()
, dus toegang tot dat vrijgemaakte geheugenblok via p
zal leiden tot ongedefinieerd gedrag . Aanwijzers die verwijzen naar geheugenelementen die zijn vrijgegeven, worden gewoonlijk bengelende wijzers genoemd en vormen een beveiligingsrisico. Bovendien stelt de C-norm dat zelfs toegang tot de waarde van een bungelende wijzer ongedefinieerd gedrag heeft. Merk op dat de aanwijzer p
zelf opnieuw kan worden gebruikt zoals hierboven weergegeven.
Houd er rekening mee dat u alleen free()
kunt aanroepen op pointers die rechtstreeks zijn geretourneerd vanuit de functies malloc()
, calloc()
, realloc()
en aligned_alloc()
, of wanneer de documentatie aligned_alloc()
dat het geheugen op die manier is toegewezen (functies zoals strdup ()
zijn opmerkelijke voorbeelden). Een aanwijzer vrijmaken,
- verkregen met de operator
&
op een variabele, of - in het midden van een toegewezen blok,
het is verboden. Een dergelijke fout wordt meestal niet door uw compiler gediagnosticeerd, maar leidt ertoe dat het programma in een ongedefinieerde staat wordt uitgevoerd.
Er zijn twee gemeenschappelijke strategieën om dergelijke gevallen van ongedefinieerd gedrag te voorkomen.
De eerste en de voorkeur is simpel - hebben p
zelf ophouden te bestaan wanneer het niet langer nodig is, bijvoorbeeld:
if (something_is_needed())
{
int *p = malloc(10 * sizeof *p);
if (p == NULL)
{
perror("malloc failed");
return -1;
}
/* do whatever is needed with p */
free(p);
}
Door free()
direct voor het einde van het bevattende blok (dwz de }
) aan te roepen, houdt p
zelf op te bestaan. De compiler geeft een compilatiefout bij elke poging om daarna p
te gebruiken.
Een tweede benadering is om de aanwijzer zelf ook ongeldig te maken na het vrijgeven van het geheugen waarnaar hij verwijst:
free(p);
p = NULL; // you may also use 0 instead of NULL
Argumenten voor deze aanpak:
Op veel platforms zal een poging om een nulaanwijzer af te leiden tot onmiddellijke crash leiden: Segmentatiefout. Hier krijgen we ten minste een stacktracering die verwijst naar de variabele die werd gebruikt nadat deze was vrijgegeven.
Zonder pointer op
NULL
, hebben we een bengelende pointer. Het programma zal hoogstwaarschijnlijk nog steeds crashen, maar later, omdat het geheugen waarnaar de aanwijzer verwijst in stilte zal zijn beschadigd. Dergelijke bugs zijn moeilijk te traceren omdat ze kunnen resulteren in een call-stack die helemaal niets te maken heeft met het oorspronkelijke probleem.Deze benadering volgt dus het fail-fast concept .
Het is veilig om een lege aanwijzer te bevrijden. De C-standaard geeft aan dat
free(NULL)
geen effect heeft:De vrije functie zorgt ervoor dat de door ptr aangewezen ruimte wordt toegewezen, dat wil zeggen beschikbaar wordt gesteld voor verdere toewijzing. Als ptr een lege aanwijzer is, vindt er geen actie plaats. Anders is het gedrag ongedefinieerd als het argument niet overeenkomt met een pointer die eerder is geretourneerd door de
calloc
,malloc
ofrealloc
, of als de ruimte is toegewezen door een call offree
ofrealloc
.
- Soms kan de eerste benadering niet worden gebruikt (bijv. Geheugen wordt toegewezen in één functie en veel later toegewezen in een volledig andere functie)
Geheugen toewijzen
Standaard toewijzing
De C dynamische geheugentoewijzingsfuncties worden gedefinieerd in de <stdlib.h>
header. Als men geheugenruimte voor een object dynamisch wil toewijzen, kan de volgende code worden gebruikt:
int *p = malloc(10 * sizeof *p);
if (p == NULL)
{
perror("malloc() failed");
return -1;
}
Dit berekent het aantal bytes dat tien int
s in het geheugen innemen, vraagt vervolgens dat veel bytes van malloc
en wijst het resultaat (dat wil zeggen, het startadres van het geheugenblok dat zojuist is aangemaakt met malloc
) toe aan een aanwijzer met de naam p
.
Het is een goede gewoonte om sizeof
te gebruiken om de hoeveelheid geheugen te berekenen die moet worden sizeof
omdat het resultaat van sizeof
implementatie is gedefinieerd (behalve tekens , char
, signed char
en unsigned char
, waarvoor sizeof
is gedefinieerd om altijd 1
te geven).
Omdat malloc
mogelijk niet in staat is om aan het verzoek te voldoen, kan het een lege aanwijzer retourneren. Het is belangrijk om dit te controleren om latere pogingen om de nulaanwijzer te verwijderen te voorkomen.
Geheugen dat dynamisch wordt toegewezen met behulp van malloc()
kan worden aangepast met behulp van realloc()
of, indien niet langer nodig, worden vrijgegeven met free()
.
Als alternatief kan int array[10];
zou dezelfde hoeveelheid geheugen toewijzen. Als het echter wordt gedeclareerd in een functie zonder het trefwoord static
, kan het alleen worden gebruikt binnen de functie waarin het wordt gedeclareerd en de functies die worden aangeroepen (omdat de array wordt toegewezen op de stapel en de ruimte wordt vrijgegeven voor hergebruik wanneer de functie keert terug). Als het alternatief wordt gedefinieerd met static
binnen een functie, of als het buiten een functie wordt gedefinieerd, is de levensduur de levensduur van het programma. Aanwijzers kunnen ook worden geretourneerd vanuit een functie, maar een functie in C kan geen array retourneren.
Geheugen op nul
Het geheugen dat door malloc
geretourneerd, is mogelijk niet op een redelijke waarde geïnitialiseerd en u moet voorzichtig zijn om het geheugen met memset
op nul te memset
of er onmiddellijk een geschikte waarde in te kopiëren. Als alternatief retourneert calloc
een blok van de gewenste grootte waarbij alle bits worden geïnitialiseerd op 0
. Dit hoeft niet hetzelfde te zijn als de weergave van drijvende komma nul of een nulaanwijzerconstante.
int *p = calloc(10, sizeof *p);
if (p == NULL)
{
perror("calloc() failed");
return -1;
}
Een opmerking over calloc
: de meeste (veelgebruikte) implementaties optimaliseren calloc()
voor prestaties, dus het is sneller dan het aanroepen van malloc()
en vervolgens memset()
, hoewel het netto-effect identiek is.
Uitgelijnd geheugen
C11 introduceerde een nieuwe functie aligned_alloc()
die ruimte toewijst aan de gegeven uitlijning. Het kan worden gebruikt als het toe te wijzen geheugen moet worden uitgelijnd op bepaalde grenzen waaraan niet kan worden voldaan door malloc()
of calloc()
. malloc()
en calloc()
functies wijzen geheugen toe dat geschikt is uitgelijnd voor elk objecttype (dwz de uitlijning is alignof(max_align_t)
). Maar met aligned_alloc()
grotere uitlijningen worden aangevraagd.
/* Allocates 1024 bytes with 256 bytes alignment. */
char *ptr = aligned_alloc(256, 1024);
if (ptr) {
perror("aligned_alloc()");
return -1;
}
free(ptr);
De C11-norm legt twee beperkingen op: 1) de gevraagde grootte (tweede argument) moet een integraal veelvoud zijn van de uitlijning (eerste argument) en 2) de waarde van de uitlijning moet een geldige uitlijning zijn die door de implementatie wordt ondersteund. Als een van beide niet wordt gehaald , leidt dit tot ongedefinieerd gedrag .
Geheugen opnieuw toewijzen
Mogelijk moet u de opslagruimte van uw aanwijzer uitbreiden of verkleinen nadat u er geheugen aan hebt toegewezen. De functie void *realloc(void *ptr, size_t size)
dealloceert het oude object waarnaar wordt ptr
met ptr
en retourneert een pointer naar een object waarvan de grootte is opgegeven door size
. ptr
is de aanwijzer naar een geheugenblok dat eerder is toegewezen met malloc
, calloc
of realloc
(of een null-aanwijzer) om opnieuw te worden toegewezen. De maximaal mogelijke inhoud van het oorspronkelijke geheugen blijft behouden. Als de nieuwe grootte groter is, wordt extra geheugen buiten de oude niet geïnitialiseerd. Als de nieuwe grootte korter is, gaat de inhoud van het gekrompen deel verloren. Als ptr
NULL is, wordt een nieuw blok toegewezen en wordt er een pointer naar teruggestuurd door de functie.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = malloc(10 * sizeof *p);
if (NULL == p)
{
perror("malloc() failed");
return EXIT_FAILURE;
}
p[0] = 42;
p[9] = 15;
/* Reallocate array to a larger size, storing the result into a
* temporary pointer in case realloc() fails. */
{
int *temporary = realloc(p, 1000000 * sizeof *temporary);
/* realloc() failed, the original allocation was not free'd yet. */
if (NULL == temporary)
{
perror("realloc() failed");
free(p); /* Clean up. */
return EXIT_FAILURE;
}
p = temporary;
}
/* From here on, array can be used with the new size it was
* realloc'ed to, until it is free'd. */
/* The values of p[0] to p[9] are preserved, so this will print:
42 15
*/
printf("%d %d\n", p[0], p[9]);
free(p);
return EXIT_SUCCESS;
}
Het opnieuw toegewezen object kan al dan niet hetzelfde adres hebben als *p
. Daarom is het belangrijk om de retourwaarde van realloc
vast te leggen die het nieuwe adres bevat als de oproep succesvol is.
Zorg ervoor dat u de retourwaarde van realloc
toewijst aan een temporary
plaats van de oorspronkelijke p
. realloc
zal null retourneren in geval van een fout, die de aanwijzer zou overschrijven. Dit zou uw gegevens verliezen en een geheugenlek veroorzaken.
Multidimensionale arrays van variabele grootte
Sinds C99 heeft C variabele lengte arrays, VLA, dat model arrays met grenzen die alleen bekend zijn bij initialisatie tijd. Hoewel je moet oppassen dat je niet te grote VLA toewijst (ze kunnen je stapel breken), is het gebruik van pointers naar VLA en het gebruik ervan in de sizeof
uitdrukkingen prima.
double sumAll(size_t n, size_t m, double A[n][m]) {
double ret = 0.0;
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
ret += A[i][j]
return ret;
}
int main(int argc, char *argv[argc+1]) {
size_t n = argc*10;
size_t m = argc*8;
double (*matrix)[m] = malloc(sizeof(double[n][m]));
// initialize matrix somehow
double res = sumAll(n, m, matrix);
printf("result is %g\n", res);
free(matrix);
}
Hier is matrix
een verwijzing naar elementen van het type double[m]
, en de sizeof
expressie met double[n][m]
zorgt ervoor dat het ruimte voor n
dergelijke elementen bevat.
Al deze ruimte wordt aaneengesloten toegewezen en kan dus worden vrijgemaakt door een free
oproep.
De aanwezigheid van VLA in de taal beïnvloedt ook de mogelijke verklaringen van arrays en pointers in functiekoppen. Nu is een algemene uitdrukking met gehele getallen toegestaan binnen de []
van matrixparameters. Voor beide functies gebruiken de uitdrukkingen in []
parameters die eerder in de parameterlijst zijn aangegeven. Voor sumAll
zijn dit de lengtes die de gebruikerscode verwacht voor de matrix. Zoals voor alle matrixfunctieparameters in C, wordt de binnenste dimensie herschreven naar een pointertype, dus dit is gelijk aan de declaratie
double sumAll(size_t n, size_t m, double (*A)[m]);
Dat wil zeggen, n
maakt niet echt deel uit van de functie-interface, maar de informatie kan nuttig zijn voor documentatie en kan ook worden gebruikt door compilers die de grenzen controleren om te waarschuwen voor toegang buiten het bereik.
Likwise voor main
de uitdrukking argc+1
is de minimale lengte die de standaard C voorschrijft voor argv
argument.
Merk op dat officiële VLA-ondersteuning optioneel is in C11, maar we kennen geen compiler die C11 implementeert en die deze niet heeft. Je zou kunnen testen met de macro __STDC_NO_VLA__
als je moet.
realloc (ptr, 0) is niet gelijk aan gratis (ptr)
realloc
is conceptueel equivalent aan malloc + memcpy + free
op de andere aanwijzer.
Als de grootte van de gevraagde ruimte nul is, is het gedrag van realloc
implementatie bepaald. Dit is vergelijkbaar voor alle geheugentoewijzing functies die ontvangen size
parameter value 0
. Zulke functies kunnen in feite een niet-nulaanwijzer opleveren, maar daar mag nooit naar worden verwezen.
Dus realloc(ptr,0)
is niet gelijk aan free(ptr)
. Het kan
- een "luie" implementatie zijn en gewoon
ptr
retourneren -
free(ptr)
, wijs een dummy-element toe en retourneer dat -
free(ptr)
en retourneer0
- retourneer gewoon
0
voor mislukking en doe niets anders.
Dus met name de laatste twee gevallen zijn niet te onderscheiden door de toepassingscode.
Dit betekent dat realloc(ptr,0)
het geheugen mogelijk niet echt vrijmaakt / dealloceert en daarom mag het nooit als free
vervanging worden gebruikt.
Door de gebruiker gedefinieerd geheugenbeheer
malloc()
roept vaak onderliggende besturingssysteemfuncties op om pagina's geheugen te verkrijgen. Maar er is niets speciaals aan de functie en deze kan in rechte C worden geïmplementeerd door een grote statische array te declareren en ervan te alloceren (er is een kleine moeilijkheid om te zorgen voor een correcte uitlijning, in de praktijk is uitlijnen tot 8 bytes bijna altijd voldoende).
Om een eenvoudig schema te implementeren, wordt een besturingsblok opgeslagen in het geheugengebied onmiddellijk vóór de aanwijzer die moet worden geretourneerd uit de oproep. Dit betekent dat free()
kan worden geïmplementeerd door van de geretourneerde aanwijzer af te trekken en de besturingsinformatie af te lezen, meestal de blokgrootte plus enkele informatie waarmee deze weer in de vrije lijst kan worden geplaatst - een gekoppelde lijst met niet-toegewezen blokken.
Wanneer de gebruiker een toewijzing aanvraagt, wordt de vrije lijst doorzocht totdat een blok van identieke of grotere grootte wordt gevonden dan het gevraagde bedrag, waarna het indien nodig wordt gesplitst. Dit kan leiden tot geheugenfragmentatie als de gebruiker voortdurend veel toewijzingen maakt en van onvoorspelbare grootte en met onvoorspelbare intervallen vrijmaakt (niet alle echte programma's gedragen zich zo, het eenvoudige schema is vaak geschikt voor kleine programma's).
/* typical control block */
struct block
{
size_t size; /* size of block */
struct block *next; /* next block in free list */
struct block *prev; /* back pointer to previous block in memory */
void *padding; /* need 16 bytes to make multiple of 8 */
}
static struct block arena[10000]; /* allocate from here */
static struct block *firstfree;
Veel programma's vereisen een groot aantal toewijzingen van kleine objecten van dezelfde grootte. Dit is heel eenvoudig te implementeren. Gebruik gewoon een blok met een volgende aanwijzer. Dus als een blok van 32 bytes vereist is:
union block
{
union block * next;
unsigned char payload[32];
}
static union block arena[100];
static union block * head;
void init(void)
{
int i;
for (i = 0; i < 100 - 1; i++)
arena[i].next = &arena[i + 1];
arena[i].next = 0; /* last one, null */
head = &block[0];
}
void *block_alloc()
{
void *answer = head;
if (answer)
head = head->next;
return answer;
}
void block_free(void *ptr)
{
union block *block = ptr;
block->next = head;
head - block;
}
Dit schema is extreem snel en efficiënt en kan generiek worden gemaakt met een zeker verlies van duidelijkheid.
alloca: wijs geheugen toe op stapel
Voorbehoud: alloca
wordt hier alleen voor de volledigheid vermeld. Het is volledig niet-draagbaar (valt niet onder de gemeenschappelijke normen) en heeft een aantal potentieel gevaarlijke functies die het onveilig maken voor onbewuste personen. Moderne C-code moet deze vervangen door Variable Length Arrays (VLA).
#include <alloca.h>
// glibc version of stdlib.h include alloca.h by default
void foo(int size) {
char *data = alloca(size);
/*
function body;
*/
// data is automatically freed
}
Wijs geheugen toe aan het stapelframe van de beller. De ruimte waarnaar de teruggekeerde aanwijzer verwijst, is automatisch vrij als de belfunctie is afgelopen.
Hoewel deze functie handig is voor automatisch geheugenbeheer, moet u er rekening mee houden dat het aanvragen van een grote toewijzing een stapeloverloop kan veroorzaken en dat u niet free
kunt gebruiken met geheugen toegewezen met alloca
(wat meer problemen met alloca
kan veroorzaken).
Om deze reden wordt het afgeraden om alloca
in een lus of een recursieve functie te gebruiken.
En omdat het geheugen free
is bij terugkeer van de functie, kunt u de aanwijzer niet retourneren als een functieresultaat ( het gedrag zou niet zijn gedefinieerd ).
Samenvatting
- oproep identiek aan
malloc
- automatisch vrijgemaakt bij terugkeer functie
- incompatibel met
free
,realloc
functies ( ongedefinieerd gedrag ) - pointer kan niet worden geretourneerd als een functieresultaat ( ongedefinieerd gedrag )
- toewijzingsgrootte beperkt door stapelruimte, die (op de meeste machines) een stuk kleiner is dan de heapruimte die beschikbaar is voor gebruik door
malloc()
- vermijd het gebruik van
alloca()
en VLA's (arrays met variabele lengte) in een enkele functie -
alloca()
is niet zo draagbaar alsmalloc()
et al
Aanbeveling
- Gebruik
alloca()
in nieuwe code
Modern alternatief.
void foo(int size) {
char data[size];
/*
function body;
*/
// data is automatically freed
}
Dit werkt waar alloca()
werkt en werkt op plaatsen waar alloca()
niet werkt (bijvoorbeeld in lussen). Er wordt uitgegaan van een C99-implementatie of een C11-implementatie die __STDC_NO_VLA__
niet definieert.