Sök…


Introduktion

För hantering av dynamiskt tilldelat minne tillhandahåller standard C-biblioteket funktionerna malloc() , calloc() , realloc() och free() . I C99 och senare är det också aligned_alloc() . Vissa system ger också alloca() .

Syntax

  • tomrum * inriktad_alloc (storlek_t justering, storlek_t storlek); / * Endast sedan C11 * /
  • void * calloc (size_t nelements, size_t size);
  • void free (void * ptr);
  • tomrum * malloc (storlek_t storlek);
  • void * realloc (void * ptr, size_t size);
  • ogiltigt * alloca (storlek_t storlek); / * från alloca.h, inte standard, inte bärbar, farlig. * /

parametrar

namn beskrivning
storlek ( malloc , realloc och aligned_alloc ) total storlek på minnet i byte. För aligned_alloc måste storleken vara en integrerad multipel av inriktningen.
storlek ( calloc ) storlek på varje element
nelements antal element
ptr pekaren till tilldelat minne som tidigare returnerats av malloc , calloc , realloc eller aligned_alloc
inriktning anpassning av tilldelat minne

Anmärkningar

C11

Observera att aligned_alloc() endast definieras för C11 eller senare.

System som de som baseras på POSIX tillhandahåller andra sätt att tilldela inriktat minne (t.ex. posix_memalign() ), och har också andra minneshanteringsalternativ (t.ex. mmap() ).

Frigör minne

Det är möjligt att frigöra dynamiskt tilldelat minne genom att ringa gratis () .

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 */

Det minne som p pekas återställs (antingen genom implementering av libc eller av det underliggande operativsystemet) efter samtalet att free() , så åtkomst till det frigjorda minnesblocket via p kommer att leda till odefinierat beteende . Pekare som hänvisar till minneselement som frigörs kallas ofta dinglande pekare och utgör en säkerhetsrisk. Vidare säger C-standarden att även tillgång till värdet på en dinglande pekare har odefinierat beteende. Observera att pekaren p själv kan ändras om som visas ovan.

Observera att du bara kan ringa free() på pekare som direkt har returnerats från malloc() , calloc() , realloc() och aligned_alloc() -funktioner, eller där dokumentationen säger att minnet har tilldelats på det sättet (funktioner som strdup () är anmärkningsvärda exempel). Frigöra en pekare som är,

  • erhålls genom att använda & operatören på en variabel, eller
  • mitt i ett tilldelat block,

är förbjudet. Ett sådant fel kommer vanligtvis inte att diagnostiseras av din kompilator utan leder programutförandet i ett odefinierat tillstånd.

Det finns två vanliga strategier för att förhindra sådana fall av odefinierat beteende.

Det första och föredragna är enkelt - har p själv upphör att existera när det inte längre behövs, till exempel:

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);
}

Genom att ringa free() direkt före slutet av det innehållande blocket (dvs } ) upphör p själv att existera. Kompilatorn ger ett sammanställningsfel vid alla försök att använda p efter det.

Ett andra tillvägagångssätt är att ogiltigförklara själva pekaren efter att ha släppt minnet som den pekar på:

free(p);
p = NULL;     // you may also use 0 instead of NULL

Argument för denna strategi:

  • På många plattformar kommer ett försök att ta bort en nollpekare att orsaka omedelbar krasch: Segmenteringsfel. Här får vi åtminstone ett stapelspår som pekar på variabeln som användes efter att ha frigjorts.

    Utan att ställa in pekaren till NULL vi dinglande pekaren. Programmet kommer troligen fortfarande att krascha, men senare, eftersom minnet till vilket pekaren pekar tyst kommer att skadas. Sådana buggar är svåra att spåra eftersom de kan resultera i en samtalstack som är helt relaterad till det ursprungliga problemet.

    Denna strategi följer följaktligen det misslyckade-snabba konceptet .

  • Det är säkert att frigöra en nollpekare. C-standarden anger att free(NULL) har någon effekt:

    Den fria funktionen gör att det utrymme som ptr påpekas omlokaliseras, det vill säga tillgängligt för ytterligare allokering. Om ptr är en nollpekare inträffar ingen åtgärd. Annars, om argumentet inte stämmer med en pekare som tidigare returnerats av calloc , malloc eller realloc funktionen, eller om utrymmet har omfördelats av ett samtal till free eller realloc , är beteendet odefinierat.

  • Ibland kan det första tillvägagångssättet inte användas (t.ex. minnes allokeras i en funktion och delas ut mycket senare i en helt annan funktion)

Tilldela minne

Standardallokering

Funktionerna för dynamisk tilldelning av C-minnet definieras i rubriken <stdlib.h> . Om man dynamiskt vill tilldela minnesutrymme för ett objekt kan följande kod användas:

int *p = malloc(10 * sizeof *p);
if (p == NULL) 
{
    perror("malloc() failed");
    return -1;
}

Detta beräknar antalet byte som tio int upptar i minnet, och begär sedan att många byte från malloc och tilldelar resultatet (dvs. startadressen för minneskortet som just skapades med malloc ) till en pekare med namnet p .

Det är bra att använda sizeof att beräkna hur mycket minne som ska begäras eftersom resultatet av sizeof är implementeringsdefinierat (med undantag för karaktärstyper , som är char , signed char och unsigned char , för vilken sizeof definieras för att alltid ge 1 ).

Eftersom malloc kanske inte kan betjäna begäran kan det returnera en nollpekare. Det är viktigt att kontrollera om detta för att förhindra senare försök att ta bort nollpekaren.

Minne dynamiskt allokerat med malloc() kan ändras med hjälp av realloc() eller, när det inte längre behövs, släpps med free() .

Alternativt förklara int array[10]; skulle tilldela samma mängd minne. Men om den deklareras i en funktion utan nyckelordet static , kommer den bara att användas inom den funktion som den deklareras i och de funktioner som den anropar (eftersom matrisen kommer att tilldelas på stacken och utrymmet släpps för återanvändning när funktionen återgår). Alternativt, om den definieras med static inuti en funktion, eller om den definieras utanför någon funktion, är dess livslängd programmets livslängd. Pekare kan också returneras från en funktion, men en funktion i C kan inte returnera en matris.

Nollminne

Det minne som returneras av malloc kanske inte initialiseras till ett rimligt värde, och försiktighet bör tas till att nollställa minnet med memset eller omedelbart kopiera ett lämpligt värde till det. Alternativt returnerar calloc ett block med önskad storlek där alla bitar initialiseras till 0 . Detta behöver inte vara detsamma som representeringen av flytande punkt noll eller en nollpekarkonstant.

int *p = calloc(10, sizeof *p);
if (p == NULL) 
{
    perror("calloc() failed");
    return -1;
}

En anmärkning om calloc : De flesta (vanligtvis) implementeringar optimerar calloc() för prestanda, så det blir snabbare än att ringa malloc() , sedan memset() , även om nettoeffekten är identisk.

Justerat minne

C11

C11 introducerade en ny funktion aligned_alloc() som fördelar utrymme med den angivna justeringen. Det kan användas om minnet som ska allokeras behövs för att justeras vid vissa gränser som inte kan uppfyllas av malloc() eller calloc() . malloc() och calloc() -funktioner allokerar minne som är lämpligt inriktat för alla objekttyper (dvs. justeringen är alignof(max_align_t) ). Men med aligned_alloc() större anpassningar begäras.

/* Allocates 1024 bytes with 256 bytes alignment. */
char *ptr = aligned_alloc(256, 1024);
if (ptr) {
    perror("aligned_alloc()");
    return -1;
}
free(ptr);

C11-standarden sätter två begränsningar: 1) storleken (det andra argumentet) som begärs måste vara en integrerad multipel av inriktningen (första argumentet) och 2) värdet på inriktningen bör vara en giltig inriktning som stöds av implementeringen. Underlåtenhet att möta någon av dem resulterar i odefinierat beteende .

Omfördela minne

Du kan behöva utöka eller krympa pekarens lagringsutrymme efter att du har tilldelat minnet till det. void *realloc(void *ptr, size_t size) det gamla objektet som ptr pekas på och returnerar en pekare till ett objekt som har storleken angiven efter size . ptr är pekaren till ett minnesblock som tidigare tilldelats malloc , calloc eller realloc (eller en nollpekare) som ska omfördelas. Det maximala möjliga innehållet i originalminnet bevaras. Om den nya storleken är större är alla extra minne utöver den gamla storleken uninitialiserade. Om den nya storleken är kortare förloras innehållet i den krympta delen. Om ptr är NULL tilldelas ett nytt block och en pekare till den returneras av funktionen.

#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;
}

Det omfördelade objektet kanske inte har samma adress som *p . Därför är det viktigt att fånga returvärdet från realloc som innehåller den nya adressen om samtalet lyckas.

Se till att du tilldelar returvärdet för realloc till en temporary istället för den ursprungliga p . realloc kommer att returnera noll i händelse av fel, vilket skulle överskriva pekaren. Detta skulle förlora dina data och skapa en minnesläcka.

Flerdimensionella matriser av variabel storlek

C99

Sedan C99 har C matriser med variabel längd, VLA, den modellen matriser med gränser som endast är kända vid initialiseringstiden. Samtidigt som du måste vara försiktig så att du inte tilldelar för stor VLA (de kan krossa din stack), är det bra att använda pekare till VLA och använda dem i sizeof uttryck.

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);
}

Här är matrix en pekare till element av typen double[m] , och sizeof uttrycket med double[n][m] säkerställer att den innehåller utrymme för n sådana element.

Allt detta utrymme tilldelas sammanhängande och kan således fördelas med ett enda samtal till free .

Närvaron av VLA på språket påverkar också de möjliga deklarationerna av matriser och pekare i funktionsrubriker. Nu är ett allmänt heltaluttryck tillåtet inom [] för matrisparametrar. För båda funktionerna använder uttryck i [] parametrar som deklarerats tidigare i parameterlistan. För sumAll dessa längder som användarkoden förväntar sig för matrisen. När det gäller alla matrisfunktionsparametrar i C skrivs den innersta dimensionen till en pekartyp, så detta motsvarar deklarationen

  double sumAll(size_t n, size_t m, double (*A)[m]);

Det vill säga n är inte riktigt en del av funktionsgränssnittet, men informationen kan vara användbar för dokumentation och den kan också användas av gränser som kontrollerar kompilatorer för att varna om åtkomst utanför gränserna.

Likwise, för main uttrycket argc+1 är den minimala längd som den C-Standarden föreskriver för argv argumentet.

Observera att officiellt VLA-stöd är valfritt i C11, men vi känner till ingen kompilator som implementerar C11 och som inte har dem. Du kan testa med __STDC_NO_VLA__ om du måste.

realloc (ptr, 0) motsvarar inte fri (ptr)

realloc motsvarar begreppsmässigt malloc + memcpy + free på den andra pekaren.

Om storleken på det begärda utrymmet är noll är realloc beteendet implementeringsdefinerat. Detta är liknande för alla minnesallokeringsfunktioner som får en size med värdet 0 . Sådana funktioner kan faktiskt returnera en icke-noll-pekare, men det får aldrig förhindras.

Således är realloc(ptr,0) inte ekvivalent med free(ptr) . Det kan

  • vara en "lat" implementering och bara returnera ptr
  • free(ptr) , fördela ett dummy-element och returnera det
  • free(ptr) och returnera 0
  • returnera bara 0 för misslyckande och gör ingenting annat.

Så särskilt de två senare fallen kan inte skiljas med tillämpningskod.

Detta innebär att realloc(ptr,0) kanske inte riktigt frigör / återfördelar minnet, och därför bör det aldrig användas som en ersättning free .

Användardefinierad minneshantering

malloc() kallar ofta underliggande operativsystemfunktioner för att få minnessidor. Men det är inget speciellt med funktionen och den kan implementeras i rak C genom att förklara en stor statisk grupp och fördela från den (det finns en liten svårighet att säkerställa korrekt justering, i praktiken är det praktiskt taget alltid tillräckligt att justera till 8 byte).

För att implementera ett enkelt schema lagras ett kontrollblock i minnesområdet omedelbart före pekaren som ska returneras från samtalet. Detta innebär att free() kan implementeras genom att subtrahera från den returnerade pekaren och avläsa kontrollinformationen, vilket vanligtvis är blockstorleken plus viss information som gör att den kan läggas tillbaka i gratislistan - en länkad lista med odelade block.

När användaren begär en allokering söks gratislistan tills ett block av identisk eller större storlek som det begärda beloppet hittas, då delas det vid behov upp. Detta kan leda till minnesfragmentering om användaren kontinuerligt gör många tilldelningar och frigör av oförutsägbar storlek och med oförutsägbara intervaller (inte alla verkliga program uppför sig så, det enkla schemat är ofta tillräckligt för små program).

/* 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;

Många program kräver ett stort antal tilldelningar av små objekt av samma storlek. Detta är mycket enkelt att implementera. Använd bara ett block med en nästa pekare. Så om ett block på 32 byte krävs:

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;
}

Detta schema är extremt snabbt och effektivt och kan göras generiskt med en viss förlust av tydlighet.

alloca: fördela minne på stacken

Varning: alloca nämns bara här för fullständighetens skull. Det är helt och hållet icke-portabelt (täcks inte av någon av de vanliga standarderna) och har ett antal potentiellt farliga funktioner som gör det osäkert för ovetande. Modern C-kod bör ersätta den med Variable Length Arrays (VLA).

Manuell sida

#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
}

Tilldela minnet på samtalens stapelram, utrymmet som den returnerade pekaren hänvisar till är automatiskt fritt när samtalsfunktionen är klar.

Medan den här funktionen är bekväm för automatisk minneshantering, måste du vara medveten om att om du begär stor allokering kan orsaka ett stacköverskridande, och att du inte kan använda free med minne tilldelat alloca (vilket kan orsaka mer problem med stackoverflöde).

Av dessa skäl rekommenderas det inte att använda alloca i en slinga eller en rekursiv funktion.

Och eftersom minnet är free 'vid funktionsåtergång kan du inte returnera pekaren som funktionsresultat ( beteendet skulle vara odefinierat ).

Sammanfattning

  • samtal identiskt med malloc
  • frigörs automatiskt vid återgång av funktionen
  • inkompatibel med free , realloc funktioner ( odefinierat beteende )
  • pekaren kan inte returneras som ett funktionsresultat ( odefinierat beteende )
  • tilldelningsstorlek begränsad av stackutrymme, som (på de flesta maskiner) är mycket mindre än den högutrymme som finns tillgänglig för malloc()
  • undvik att använda alloca() och VLA: er (variabla längd arrayer) i en enda funktion
  • alloca() är inte lika bärbar som malloc() et al

Rekommendation

  • Använd inte alloca() i ny kod
C99

Modernt alternativ.

void foo(int size) {
    char data[size];
    /*
      function body;
    */
    // data is automatically freed
}

Det här fungerar där alloca() gör och fungerar på platser där alloca() inte gör det (till exempel inre slingor). Det antar antingen en C99-implementering eller en C11-implementering som inte definierar __STDC_NO_VLA__ .



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow