Dynamisch geheugenbeheer in C

Leer hoe een C-programma tijdens de uitvoering geheugen aanvraagt en teruggeeft. Vergelijk de stapel en de heap, reserveer blokken met malloc en calloc, wijzig hun grootte met realloc en geef ze vrij met free. Herken geheugenlekken, pointers naar niet meer bestaande objecten, dubbele vrijgave en toegang buiten de grenzen. Begrijp waarom die crashes zoals segmentatiefouten veroorzaken en spoor ze op en herstel ze met Valgrind en AddressSanitizer.

BeginnerLes 5 van 8 in Cursus Programmeren in C: pointers, geheugenbeheer en debuggen

Wat je leert

  1. Beschrijf de stapel, de heap en het statische geheugen, hoelang gegevens op elk van die plekken bestaan en wanneer een programma heapgeheugen nodig heeft.
    • Vergelijk stapelgeheugen, heapgeheugen en statische opslag
      Beschrijf hoe automatische, statische en toegewezen opslag verschillen in levensduur en wie ze beheert, en wat er bij functieaanroepen met elk ervan gebeurt.
    • Bepaal wanneer gegevens op de heap thuishoren
      Kies tussen lokale variabelen en geheugenallocatie op de heap op basis van de grootte, levensduur en groei van de gegevens, en voorkom dat je het adres van een lokale variabele teruggeeft.
  2. Reserveer blokken van de juiste grootte met malloc en calloc, controleer of elke aanvraag is geslaagd en geef elk blok precies één keer vrij onder verantwoordelijkheid van een duidelijke eigenaar.
    • Vraag geheugen aan met malloc en sizeof
      Bereken het aantal aan te vragen bytes, roep malloc aan, controleer op fouten en gebruik het blok als array.
    • Vraag met nullen gevuld geheugen aan met calloc
      Gebruik calloc om geheugen te reserveren voor arrays waarvan de elementen bij nul beginnen, en weet wanneer dit een betere keuze is dan malloc.
    • Geef geheugen vrij met free en houd het eigenaarschap bij
      Geef elk blok precies één keer op het juiste moment vrij, bepaal welk deel van het programma eigenaar is van elk blok en ruim correct op als een geheugenallocatie mislukt.
  3. Vergroot en verklein heapblokken zonder gegevens of geheugen te verliezen en maak arrays die meegroeien als er gegevens binnenkomen.
    • Vergroot en verklein een geheugenblok veilig met realloc
      Roep realloc aan via een tijdelijke pointer, behoud de gegevens en het oorspronkelijke blok als de aanroep mislukt, en werk alle pointers bij nadat het blok is verplaatst.
    • Maak een uitbreidbare array
      Houd naast de pointer een lengte en een capaciteit bij, laat de capaciteit meetkundig groeien en lees invoer van onbekende lengte in een buffer die meegroeit.
  4. Herken geheugenlekken, pointers naar niet meer bestaande objecten, dubbele vrijgave, toegang buiten de grenzen en het lezen van niet-geïnitialiseerde waarden in C-code, leg uit waarom ze gevaarlijk zijn en herleid een segmentatiefout tot de oorzaak.
    • Spoor geheugenlekken op en voorkom ze
      Leg uit wat een geheugenlek is, herken codepatronen die geheugenlekken veroorzaken en pas werkwijzen toe die ze voorkomen.
    • Voorkom pointers naar niet meer bestaande objecten, gebruik na vrijgave en dubbele vrijgave
      Herken pointers naar geheugen waarvan de levensduur is geëindigd, leg uit waarom het gebruik of opnieuw vrijgeven ervan ongedefinieerd gedrag is en pas werkwijzen toe die dit voorkomen.
    • Blijf binnen het geheugenblok: grensoverschrijdingen en het lezen van niet-geïnitialiseerde waarden
      Blijf bij elke toegang binnen de grenzen van het blok, bepaal de juiste grootte voor stringbuffers en stel geheugen in voordat je het leest.
    • Verklaar segmentatiefouten en spoor de oorzaak op
      Leg uit wat een segmentatiefout is, noem veelvoorkomende oorzaken en werk vanaf de crash terug naar de regel met de fout.
  5. Voer programma’s uit onder Valgrind Memcheck en met AddressSanitizer, lees hun rapporten en gebruik die om geheugenlekken en ongeldige geheugentoegang te herstellen totdat een programma zonder fouten draait.
    • Controleer een programma met Valgrind Memcheck
      Bouw een programma voor Valgrind, voer het uit met geheugenlekcontrole en lees de foutmeldingen, het heapoverzicht en de geheugenlekcategorieën.
    • Gebruik AddressSanitizer en LeakSanitizer
      Bouw een programma met AddressSanitizer, lees de rapporten en kies tussen sanitizers en Valgrind.
    • Herstel geheugenfouten aan de hand van een hulpmiddelrapport
      Volg een herhaalbare werkwijze van een hulpmiddelrapport naar de regel met de fout, herstel de oorzaak in plaats van het symptoom en bevestig de oplossing met een uitvoering zonder fouten.

Dynamisch geheugenbeheer in C: uitleg

5 hoofdstukken

1

Leg uit waar een C-programma zijn gegevens opslaat

Elk object heeft een levensduur en een pointer is alleen bruikbaar zolang het object waarnaar die wijst bestaat. Lokale variabelen, statische gegevens en toegewezen blokken volgen verschillende regels voor hun levensduur. Door die regels te verbinden met de stapel en de heap begrijp je waarom een functie een toegewezen array kan teruggeven, maar geen bruikbaar adres van haar eigen lokale array. De grootte, levensduur en latere groei bepalen welke opslag geschikt is voor een taak.

1.1 Opslag en levensduur

Het adres van een variabele vertelt je waar de gegevens staan. De opslagduur vertelt je hoelang die gegevens bestaan. Een adres bewaren houdt het object niet in leven.

Drie levensduren
OpslagduurTypische objectenLevensduur eindigt
AutomatischGewone lokale variabelen en parametersAls hun blok eindigt
StatischGlobale variabelen en lokale variabelen gedeclareerd met staticAls het programma eindigt
ToegewezenBlokken verkregen via geheugenallocatiefunctiesAls het blok wordt vrijgegeven

Op gangbare systemen gebruiken automatische objecten de aanroepstapel en toegewezen blokken de heap. C bepaalt de levensduren, maar vereist deze fysieke gebieden niet. Machinecode en statische gegevens nemen andere gebieden in, waarvan de indeling afhangt van het systeem.

Een functieaanroep maakt normaal gesproken een stackframe met de parameters, lokale variabelen en terugkeerinformatie. Aanroepen worden genest: het meest recente frame wordt als eerste verwijderd. Bij terugkeer wordt de automatische opslag van die aanroep vrijgegeven, zodat latere aanroepen de ruimte opnieuw kunnen gebruiken. De allocator van de heap beheert een afzonderlijke geheugenvoorraad. Die reserveert op aanvraag een aaneengesloten blok en geeft het beginadres. Terugkeren uit een functie geeft dat blok niet vrij.

De pointer en zijn blok zijn afzonderlijke objecten

Bekijk deze functie, gedeclareerd met <stdlib.h> beschikbaar:

int *make_array(size_t n) {
    if (n == 0 || n > SIZE_MAX / sizeof(int)) return NULL;
    int *p = malloc(n * sizeof *p);
    if (p == NULL) return NULL;
    return p;
}

SIZE_MAX komt uit <stdint.h>. Als main int *nums = make_array(5); aanroept, horen de parameter n en de pointervariabele p bij de aanroep. De toegewezen array hoort daar niet bij. Als een int 4 bytes is, bevat het blok 20 bytes. Bij het terugkeren wordt het adres naar nums gekopieerd; de array wordt niet gekopieerd. De aanroepende functie controleert nums, gebruikt de elementen nadat ze zijn ingesteld en geeft uiteindelijk het blok vrij.

De lokale variabele p houdt op te bestaan als de functie terugkeert. Het blok blijft bestaan omdat het toegewezen opslagduur heeft, geen automatische. Dit onderscheid werkt ook andersom: een lokale pointer kan naar een globaal object wijzen en een globale pointer kan naar een heapblok wijzen.

Statische gegevens blijven bestaan tussen aanroepen
int next_id(void) {
    static int id = 0;
    return ++id;
}

Opeenvolgende aanroepen geven 1, 2 en 3 terug. De initialisator wordt één keer uitgevoerd en id bestaat gedurende de hele uitvoering van het programma. Als je static verwijdert, wordt bij elke aanroep een nieuwe automatische id gemaakt, geïnitialiseerd op nul; elke aanroep geeft dan 1 terug.

De stapelruimte is beperkt

Een grote lokale array en diep geneste aanroepen gebruiken allebei stapelruimte. Overschrijding van de limiet heet stapeloverloop en beëindigt meestal het programma. Veel Linux-configuraties staan een stapel van 8 MiB toe; de standaard voor de hoofdthread gereserveerde stapel van Microsoft is gewoonlijk 1 MiB. Dit zijn systeeminstellingen, geen C-regels. Een lokale array van 4.000.000 ints heeft 16.000.000 bytes nodig als een int 4 bytes is, ongeveer 15,3 MiB.

Geheugenallocatie op de heap brengt administratiekosten mee bij het reserveren en vrijgeven van blokken. Het lezen van een element is niet vanzelf langzamer alleen omdat de array op de heap staat. De heap als geheugenvoorraad staat ook los van de heapdatastructuur die in een prioriteitswachtrij wordt gebruikt.

Belangrijkste punten

  • De opslagduur van een object bepaalt zijn levensduur.
  • Automatische lokale variabelen houden op te bestaan als hun blok eindigt; statische objecten bestaan zolang het programma draait.
  • Een lokale pointer en het heapblok waarnaar die wijst, hebben afzonderlijke levensduren.
  • Grote lokale arrays en diepe aanroepen kunnen de stapel uitputten.
  • C specificeert levensduren, terwijl stapel en heap gangbare implementaties beschrijven.

1.2 De juiste opslag kiezen

Kies opslag door je af te vragen hoeveel ruimte nodig is, wanneer die grootte bekend wordt en hoelang de gegevens moeten bestaan. Het eenvoudigste geschikte ontwerp is vaak een gewone lokale array.

Stem de opslag af op de gegevens
BehoefteGeschikte eerste keuze
Een kleine vaste array die tijdens één aanroep wordt gebruiktLokale array
Een aantal dat uit invoer wordt gelezenGecontroleerde geheugenallocatie op de heap
Een resultaat dat langer bestaat dan de functie die het maaktGeheugenallocatie op de heap of een buffer van de aanroepende functie
Gegevens die groeien naarmate invoer binnenkomtBuffer op de heap waarvan de grootte kan worden gewijzigd
Een zeer grote arrayGeheugenallocatie op de heap met een foutcontrole

Voor de dagelijkse temperaturen van een week zijn zeven waarden nodig, dus double temps[7]; is een logische lokale array. Voor een cijfer per student is een grootte nodig die tijdens de uitvoering wordt bepaald. Gebruik bij n studenten double *grades = malloc(n * sizeof *grades); nadat je de grootte hebt gecontroleerd. Voor 250 studenten en doubles van 8 bytes vraagt dat 2.000 bytes aan. Controleer daarna het resultaat voordat je de array vult.

Waarom een lokale buffer teruggeven misgaat
char *greeting(void) {
    char buf[16] = "hello";
    return buf;  /* error: buf ends when this call returns */
}

Het teruggegeven adres kan de levensduur van buf niet verlengen. De aanroepende functie krijgt een ongeldige pointer naar een niet meer bestaand object, ook als de oude bytes ongewijzigd lijken. GCC en Clang kunnen voor dit patroon waarschuwen.

Een oplossing is de aanroepende functie opslag te laten aanleveren:

#include <string.h>

void greeting(char out[16]) {
    strcpy(out, "hello");
}

/* In the caller: */
char message[16];
greeting(message);

Hier blijft de array van de aanroepende functie bestaan tijdens de hele aanroep en daarna. De parameter gedraagt zich nog steeds als een pointer; de aanroepende functie moet voldoende ruimte aanleveren. Een tweede ontwerp reserveert een resultaat op de heap en maakt de aanroepende functie verantwoordelijk voor het vrijgeven. Een derde geeft een statische buffer terug. Die buffer blijft bestaan, maar alle aanroepende functies delen die en een latere aanroep kan de inhoud overschrijven. Kies op basis van de vraag of aanroepende functies onafhankelijke resultaten nodig hebben.

Een grootte tijdens de uitvoering betekent niet altijd de heap

C99 introduceerde arrays met variabele lengte, zoals int a[n];. Ze hebben automatische opslagduur, meestal op de stapel. C11 maakte ondersteuning optioneel en de compiler van Microsoft ondersteunt ze niet. Grote invoer kan de stapel uitputten en de declaratie van de array geeft geen nulresultaat dat je kunt controleren. Geheugenallocatie op de heap biedt een controleerbaar foutpad en ondersteunt het later wijzigen van de grootte.

Een pointer geeft geen eigenaarschap aan
int value = 3;
int *p = &value;

p wijst naar een automatisch object. Die pointer aan free doorgeven is ongeldig. Dezelfde regel sluit het vrijgeven van een statische array of een stringliteral uit. Het adres moet horen bij een nog bestaand toegewezen blok waarvan het programma eigenaar is.

Ook wijst char *s = "hello"; naar een stringliteral; het maakt geen beschrijfbare kopie. Het wijzigen van de literal is ongedefinieerd gedrag. Gebruik char s[] = "hello"; voor een beschrijfbare lokale kopie, of reserveer ruimte voor een afzonderlijke kopie op de heap.

Belangrijkste punten

  • Kies opslag op basis van de grootte, de vereiste levensduur en de noodzaak om te groeien.
  • Een door een functie teruggegeven adres is alleen bruikbaar als het object waarnaar het wijst nog bestaat.
  • Bij een buffer die de aanroepende functie beschikbaar stelt, blijft het eigenaarschap bij die functie.
  • Een statische resultaatbuffer wordt gedeeld tussen aanroepen.
  • Pointers kunnen naar lokale, statische of toegewezen objecten wijzen; alleen nog bestaande toegewezen blokken kunnen worden vrijgegeven.
  • Stringliterals zijn geen beschrijfbare kopieën.
2

Reserveer heapgeheugen en geef het vrij

Een geheugenaanvraag heeft een byteaantal, een foutpad en een eigenaar nodig. Ga uit van het aantal elementen om de benodigde opslag te bepalen, beslis of hun beginwaarden nul moeten zijn en gebruik elk blok pas na een geslaagde aanvraag. Eigenaarschap verbindt het laatste gebruik vervolgens met het opruimen, ook bij geneste geheugenallocaties en functies die hun werk maar gedeeltelijk kunnen uitvoeren.

2.1 Het juiste aantal bytes aanvragen

malloc reserveert een aaneengesloten blok bytes en geeft het beginadres terug. Neem <stdlib.h> op voor de declaratie. Een geslaagde geheugenallocatie levert opslag op, maar vult die opslag niet met bruikbare waarden.

Tel de elementen en bepaal hun grootte
int *nums = malloc(n * sizeof *nums);

n is een aantal elementen; sizeof *nums is het aantal bytes per element. Vermenigvuldigen levert het aantal bytes op. Als een int 4 bytes is, hebben tien ints 40 bytes nodig. Tien doubles van 8 bytes hebben 80 bytes nodig. Tien pointers hebben 80 bytes nodig als elke pointer 8 bytes is. Een char is altijd één C-byte.

De vorm sizeof *nums volgt het type waarnaar wordt gewezen als de declaratie verandert. Voor dit gewone type zonder variabele lengte evalueert sizeof de expressie *nums niet en derefereert dus geen niet-geïnitialiseerde pointer. In C wordt de teruggegeven void * zonder expliciete typeconversie geconverteerd naar het objectpointertype.

malloc(n) reserveert n bytes, geen ruimte voor n ints. sizeof nums meet de pointervariabele, niet een element of het hele blok. Op een systeem met pointers van 8 bytes en ints van 4 bytes vraagt malloc(n * sizeof nums) tweemaal de benodigde ruimte aan, waardoor de verkeerde expressie verborgen blijft. Bij andere typen kan die te weinig reserveren.

Controleer de grootte en fouten vóór gebruik

Dit volledige programma wijst een lege aanvraag af, beveiligt de vermenigvuldiging tegen overloop, controleert de geheugenallocatie en initialiseert elk element:

#include <stdint.h>
#include <stdlib.h>

int main(void) {
    size_t n = 5;
    int *nums;
    if (n == 0 || n > SIZE_MAX / sizeof *nums) {
        return EXIT_FAILURE;
    }
    nums = malloc(n * sizeof *nums);
    if (nums == NULL) return EXIT_FAILURE;

    for (size_t i = 0; i < n; ++i) {
        nums[i] = (int)(i * i);
    }
    free(nums);
    return EXIT_SUCCESS;
}

SIZE_MAX, uit <stdint.h>, is de grootste size_t-waarde. Controleer met een deling vóór de vermenigvuldiging. Als het product eerst modulair terugloopt, kun je de bedoelde grootte niet herstellen door het kleinere teruggelopen resultaat te controleren. Een aanvraag met een grootte ongelijk aan nul kan mislukken, ook als de berekening binnen het bereik past. Daarom is de controle op een nulpointer een afzonderlijke stap.

De geïnitialiseerde waarden zijn hier 0, 1, 4, 9 en 16. nums[i] bereikt hetzelfde element als *(nums + i). Pointerrekenen gaat in stappen van elementen, dus de code vermenigvuldigt i niet met de grootte in bytes.

Houd het aantal bij de pointer

Er is geen standaardbewerking in C die het aantal elementen van een geheugenallocatie uit de pointer kan afleiden. Bewaar n afzonderlijk en geef zowel de pointer als het aantal door aan functies. Het aantal bepaalt de lussen en indexcontroles.

Een gekopieerde string heeft ruimte nodig voor de tekens en het afsluitende '\0'. De string “Amsterdam” heeft negen tekens en heeft tien bytes nodig, dus vraag strlen(s) + 1 aan voor de kopie. <string.h> levert strlen.

Een malloc-aanvraag van grootte nul kan een nulpointer teruggeven of een pointer die kan worden vrijgegeven maar niet mag worden gederefereerd. Behandel een aantal van nul als een expliciet leeg geval in plaats van zo’n pointer als bruikbare arrayopslag te behandelen.

Belangrijkste punten

  • Vermenigvuldig voor het aantal bytes van een array het aantal elementen met sizeof *pointer.
  • Controleer op overloop bij de vermenigvuldiging voordat je malloc aanroept.
  • Controleer op NULL voordat je het resultaat gebruikt.
  • Initialiseer elk malloc-element voordat je het leest.
  • Bewaar het aantal elementen afzonderlijk; sizeof pointer kan het niet achterhalen.
  • Tel het afsluitende nulteken mee wanneer je geheugen reserveert voor een kopie van een string.
  • Behandel een aantal van nul expliciet.

2.2 Een array bij nul laten beginnen

Een teller heeft een bepaalde beginwaarde nodig vóór de eerste verhoging. calloc combineert geheugenallocatie met het op nul zetten, zodat een array van gehele tellers na de foutcontrole klaar is om te tellen.

Twee argumenten beschrijven de array

Neem <stdlib.h> op en schrijf:

int *tally = calloc(26, sizeof *tally);
if (tally == NULL) return EXIT_FAILURE;

Het eerste argument is het aantal elementen en het tweede is de grootte van één element. Als een int 4 bytes is, reserveert dit 104 bytes. Elke byte begint op nul, dus elke gehele teller begint op nul. Een aanroep van calloc met één gecombineerd argument compileert niet; de interface vereist twee argumenten.

Verhoog voor het woord “banana” de teller die bij elke letter hoort. Na verwerking bevatten de tellers voor a, b en n de waarden 3, 1 en 2. Alle andere blijven nul. Bij een tekencodering met opeenvolgende kleine letters is de indexexpressie c - 'a'. De invoer moet een kleine letter zijn voordat die expressie als array-index wordt gebruikt.

#include <stdlib.h>

int main(void) {
    int *counts = calloc(3, sizeof *counts);
    if (counts == NULL) return EXIT_FAILURE;
    counts[1]++;
    counts[1]++;
    /* The counters now hold 0, 2, 0. */
    free(counts);
    return EXIT_SUCCESS;
}

Alleen de middelste teller is door de telcode beschreven. De twee tellers ernaast hebben nog steeds bruikbare waarden, omdat ze bij de geheugenallocatie op nul zijn gezet.

Wat met nullen gevuld betekent

calloc zet elke bit op nul. Dit levert nulwaarden op voor elementen van gehele typen en van char. Een met nullen gevulde tekenbuffer met een grootte groter dan nul begint daarom als een lege string: het eerste teken is '\0'.

Op gangbare platforms stellen uitsluitend nulbits ook drijvendekommanul en nulpointers voor. Overdraagbare C-code kan voor drijvendekomma- en pointertypen niet op die representaties rekenen. Als code zulke waarden in alle C-implementaties nodig heeft, wijs dan expliciet 0.0 of NULL toe aan de elementen.

Kiezen tussen de geheugenallocatiefuncties
Begin van het gebruikLogische keuze
Gehele tellers verhogencalloc
Gehele vlaggen geleidelijk instellen vanuit een toestand met alleen nullencalloc
Elk element direct overschrijvenmalloc

Een geslaagde malloc gevolgd door memset kan een blok op nul zetten, maar de vermenigvuldiging voor de grootte in die code moet nog steeds tegen overloop worden beveiligd. Gangbare implementaties van calloc wijzen een product af dat te groot is voor size_t; C23 vereist dit expliciet. Het resultaat moet nog steeds op een nulpointer worden gecontroleerd, omdat een aanvraag met een correcte grootte kan mislukken.

Het op nul zetten geldt voor deze geheugenallocatie. Als realloc het blok later vergroot, zijn de toegevoegde bytes niet geïnitialiseerd. Stel die elementen in voordat je ze verhoogt of leest. Geef het blok ten slotte vrij met free, net als een blok dat met malloc is verkregen.

Belangrijkste punten

  • Geef calloc een aantal elementen en een elementgrootte door.
  • Gehele tellers die met nullen zijn gevuld, kun je direct na de controle van de geheugenallocatie verhogen.
  • Bytes met alleen nullen zijn geen overdraagbare vervanging voor het toewijzen van drijvendekommanul of nulpointers.
  • Bij groei met realloc worden de toegevoegde bytes niet op nul gezet.

2.3 Eén eigenaar en één laatste vrijgave

Voor een toegewezen blok moet duidelijk zijn: “Wie geeft dit vrij?” Die verantwoordelijkheid heet eigenaarschap. Andere code mag het adres lenen zonder toestemming te krijgen om het blok vrij te geven.

Geef vrij na het laatste gebruik

free, gedeclareerd in <stdlib.h>, maakt een nog bestaand toegewezen blok beschikbaar voor hergebruik. Geef het beginadres precies één keer door. free(NULL) doet niets. Een lokaal adres, een stringliteral, een adres binnen een blok zoals p + 1, of een al vrijgegeven blok doorgeven is ongedefinieerd gedrag.

free(p) wijst niets toe aan de variabele p en garandeert niet dat de bytes worden gewist. Na vrijgave is de oude pointerwaarde ongeldig. Als de variabele in gebruik blijft, wijs je p = NULL; toe zonder de oude waarde te bekijken. Dit verandert alleen p; aliassen mogen het vrijgegeven blok nog steeds niet meer gebruiken.

Een functie die nieuw toegewezen geheugen teruggeeft, draagt het eigenaarschap over aan de aanroepende functie:

#include <stdlib.h>
#include <string.h>

char *copy_string(const char *s) {
    char *copy = malloc(strlen(s) + 1);
    if (copy == NULL) return NULL;
    strcpy(copy, s);
    return copy;
}

De aanroepende functie controleert het resultaat, gebruikt de kopie en geeft die vrij. De invoerstring is geleend; deze functie geeft die niet vrij. POSIX en C23 bieden strdup met dezelfde verdeling van het eigenaarschap; de functie hierboven werkt ook in C17.

Geef geneste geheugenallocaties van binnen naar buiten vrij

Een raster dat is opgebouwd uit een array van rijpointers heeft afzonderlijke blokken. Drie rijpointers en drie rijen van vier ints vereisen vier geheugenallocaties. Met pointers van 8 bytes en ints van 4 bytes gebruikt het buitenste blok 24 bytes en gebruiken de rijen 48 bytes, in totaal 72 bytes.

Geef elke rij vrij zolang het adres nog beschikbaar is en geef daarna de pointerarray vrij. Het vrijgeven van het buitenste blok geeft de rijen niet recursief vrij. Als je grid[i] benadert nadat je grid hebt vrijgegeven, gebruik je vrijgegeven opslag.

Ruim op bij gedeeltelijk succes

Dit volledige voorbeeld houdt bij hoeveel rijen er daadwerkelijk bestaan:

#include <stdlib.h>

int main(void) {
    size_t rows = 3, made = 0;
    int **grid = malloc(rows * sizeof *grid);
    if (grid == NULL) return EXIT_FAILURE;

    for (; made < rows; ++made) {
        grid[made] = malloc(4 * sizeof *grid[made]);
        if (grid[made] == NULL) break;
    }
    int status = made == rows ? EXIT_SUCCESS : EXIT_FAILURE;
    /* Use the grid here only if every row was allocated. */
    for (size_t i = 0; i < made; ++i) free(grid[i]);
    free(grid);
    return status;
}

Als de geheugenallocatie voor de tweede rij mislukt, is made 1, dus wordt alleen rij nul vrijgegeven. Er wordt geen niet-geïnitialiseerde rijpointer gelezen. Een ander bruikbaar ontwerp zet onafhankelijke pointers aanvankelijk op NULL en stuurt elke fout naar één opruimsectie; free(NULL) maakt die sectie veilig na gedeeltelijk succes.

Tel levensduren van eigenaarschap, geen geheugenallocatieaanroepen. Een buffer die met malloc is gemaakt en waarvan de grootte twee keer met succes is gewijzigd, heeft één laatste free op het nieuwste resultaat nodig; een geslaagde realloc beëindigt de levensduur van het vorige blok al. De allocator kan vrijgegeven ruimte bewaren voor latere geheugenallocaties, dus een systeemmonitor hoeft geen onmiddellijke daling van het procesgeheugen te tonen.

Belangrijkste punten

  • De eigenaar geeft elk blok na het laatste gebruik vrij.
  • Alleen het beginadres van een nog bestaand toegewezen blok, of NULL, is een geldig argument voor free.
  • Eén pointer terugzetten herstelt geen aliassen.
  • Geef binnenste blokken vrij vóór het buitenste blok dat hun adressen bevat.
  • Foutpaden moeten alle eerder verkregen blokken vrijgeven.
  • Na geslaagde wijzigingen van de grootte is uiteindelijk één free van het nieuwste blok nodig.
3

Wijzig de grootte van buffers met realloc

Invoer komt niet altijd binnen met een bekende uiteindelijke grootte. Met een buffer waarvan de grootte kan worden gewijzigd, kan een programma meer ruimte reserveren naarmate duidelijk wordt hoeveel nodig is. Veilig vergroten behoudt zowel bestaande waarden als de mogelijkheid om van een fout te herstellen. Door een pointer, een lengte en een capaciteit bij elkaar te houden, maak je die regels concreet, terwijl meetkundige groei de hoeveelheid herhaald kopieerwerk beperkt.

3.1 De grootte wijzigen zonder het oude blok te verliezen

realloc wijzigt de grootte van een toegewezen blok. De functie kan de opslag vergroten of verkleinen en het teruggegeven adres kan in beide gevallen veranderen. Neem <stdlib.h> op en behandel succes en falen als verschillende gebeurtenissen in de levensduur.

Wat blijft en wat verandert

Als een blok groeit, blijft de bestaande inhoud behouden tot aan de oude grootte. Toegevoegde bytes zijn niet geïnitialiseerd. Als het blok krimpt, blijft alleen het begindeel behouden dat in de nieuwe grootte past. Een array met 3, 1, 4 en 1 behoudt die vier waarden bij vergroting naar zes ints; de laatste twee elementen moeten vóór gebruik een waarde krijgen. Bij ints van 4 bytes verandert de aanvraag daarmee van 16 naar 24 bytes.

De allocator kan het blok op dezelfde plek uitbreiden, of elders geheugen reserveren, de behouden bytes kopiëren en het oude blok vrijgeven. Je code moet met beide resultaten werken.

Vang het resultaat op in een tijdelijke pointer
int *tmp = realloc(nums, 6 * sizeof *nums);
if (tmp == NULL) {
    /* nums still owns its original block. */
} else {
    nums = tmp;
    nums[4] = 5;
    nums[5] = 9;
}

Bij deze aanvraag met een grootte groter dan nul blijft het oorspronkelijke blok bij een fout gereserveerd en ongewijzigd. Het programma kan het blijven gebruiken of vrijgeven voordat het de fout meldt. Bij succes gaat het eigenaarschap over op het teruggegeven blok. Wijs het nieuwe resultaat toe voordat je de array met gewijzigde grootte benadert.

De directe toewijzing nums = realloc(nums, bytes); verliest deze bescherming. Als de aanroep mislukt, overschrijft die het enige opgeslagen adres met NULL. Het oude blok blijft gereserveerd, maar het programma kan het niet meer vrijgeven.

Opgeslagen pointers naar elementen moeten opnieuw worden berekend

Stel dat code int *selected = &nums[2]; opslaat. Een geslaagde wijziging van de grootte beëindigt de levensduur van het oude object, ook als het teruggegeven adres toevallig gelijk is. selected is na dat succes ongeldig. Bewaar index 2 tijdens de wijziging en bereken daarna selected = &nums[2]; vanuit de nieuwe pointer, als die index nog bestaat.

Vergelijk de oude pointer niet met het resultaat om te bepalen of aliassen moeten worden bijgewerkt. Werk bij elk succes vanuit het nieuwe resultaat. Als het geselecteerde element bij het verkleinen is verwijderd, is er geen elementpointer om opnieuw te berekenen.

Een volledig voorbeeld van vergroten en vrijgeven
#include <stdlib.h>

int main(void) {
    int *nums = malloc(4 * sizeof *nums);
    if (nums == NULL) return EXIT_FAILURE;
    for (size_t i = 0; i < 4; ++i) nums[i] = (int)i;

    int *tmp = realloc(nums, 6 * sizeof *nums);
    if (tmp == NULL) {
        free(nums);
        return EXIT_FAILURE;
    }
    nums = tmp;
    nums[4] = 4;
    nums[5] = 5;
    free(nums);
    return EXIT_SUCCESS;
}

Controleer bij een variabel aantal vóór deze aanroep de vermenigvuldiging naar bytes, net als bij malloc. Vraag bytes aan, geen elementen: realloc(nums, 6) vraagt zes bytes aan.

realloc(NULL, positive_size) gedraagt zich als malloc, zodat een lege buffer met een nulpointer kan beginnen. Roep free expliciet aan om een buffer vrij te geven. Vermijd het wijzigen van de grootte naar nul: bibliotheken gedroegen zich in het verleden verschillend en het wijzigen van een bestaand blok naar grootte nul is ongedefinieerd gedrag in C23. Lokale arrays en eerder vrijgegeven blokken zijn beide geen geldige objecten om op deze manier van grootte te veranderen.

Belangrijkste punten

  • Gebruik een tijdelijke pointer zodat bij een mislukte wijziging naar een grootte groter dan nul het adres van de eigenaar behouden blijft.
  • Bij vergroten blijft de oude inhoud behouden, maar zijn de toegevoegde bytes niet geïnitialiseerd.
  • Bij verkleinen blijft alleen het deel behouden dat nog past.
  • Gebruik na succes de teruggegeven pointer en bereken opgeslagen pointers naar elementen opnieuw.
  • Gebruik free om opslag vrij te geven; behandel een grootte van nul expliciet.

3.2 Een array die met de invoer meegroeit

Een uitbreidbare array heeft 3 gegevens nodig om de toestand bij te houden: de pointer, de lengte en de capaciteit. De lengte telt de geïnitialiseerde elementen die in gebruik zijn. De capaciteit telt de gereserveerde plaatsen voor elementen. Houd de lengte kleiner dan of gelijk aan de capaciteit.

Voeg in een veilige volgorde toe

Begin met int *data = NULL; size_t len = 0, cap = 0;. Controleer vóór een toevoeging of len == cap. Een volle array moet groeien voordat je naar data[len] schrijft. Pas als het wijzigen van de grootte slaagt, mogen de pointer en capaciteit veranderen. Sla de nieuwe waarde op en verhoog daarna de lengte.

Het volgende volledige voorbeeld voegt tien waarden toe met meetkundige groei:

#include <stdint.h>
#include <stdlib.h>

int main(void) {
    int *data = NULL;
    size_t len = 0, cap = 0;
    for (int value = 0; value < 10; ++value) {
        if (len == cap) {
            size_t next;
            if (cap == 0) {
                next = 4;
            } else {
                if (cap > SIZE_MAX / 2 / sizeof *data) {
                    free(data);
                    return EXIT_FAILURE;
                }
                next = cap * 2;
            }
            int *tmp = realloc(data, next * sizeof *data);
            if (tmp == NULL) {
                free(data);
                return EXIT_FAILURE;
            }
            data = tmp;
            cap = next;
        }
        data[len++] = value;
    }
    free(data);
    return EXIT_SUCCESS;
}

De overloopcontrole controleert zowel de verdubbeling als de omzetting naar bytes voordat een van beide producten modulair kan teruglopen. Ook de begincapaciteit van vier past bij dit elementtype. Bij een fout geeft het programma het oude blok, waarvan het nog eigenaar is, vrij en stopt het; het werkt de capaciteit niet ten onrechte bij.

Volg lengte en capaciteit afzonderlijk

Bij een begincapaciteit van 4 gebruiken de eerste vier toevoegingen bestaande ruimte. Vóór de vijfde verdubbelt de capaciteit naar 8. Vóór de negende verdubbelt die naar 16. Tien toevoegingen eindigen met lengte 10 en capaciteit 16. Als beide vergrotingen tot een verplaatsing leiden, kopiëren ze hoogstens vier en acht elementen, in totaal twaalf.

Overcapaciteit is bewust gekozen. Bij elke toevoeging met één element groeien veroorzaakt herhaald kopiëren. Vanaf begincapaciteit 1 kan het bereiken van 1.000 elementen 999 wijzigingen van de grootte en 499.500 gekopieerde elementen vereisen. Verdubbelen bereikt capaciteit 1.024 na tien wijzigingen van de grootte en hoogstens 1.023 kopieën. Een afzonderlijke wijziging kan duur zijn, maar bij meetkundige groei blijft het gemiddelde werk per toevoeging constant.

Een factor zoals 1,5 laat minder ongebruikte ruimte over, maar vergroot vaker. Als de invoer is geëindigd, kan het programma verkleinen tot de gebruikte lengte en een fout afhandelen met hetzelfde patroon van een tijdelijke pointer. Een lege array wordt vrijgegeven met free, niet door de grootte naar nul te wijzigen.

Tekenbuffers hebben één extra plaats nodig

Reserveer voor een groeiende string ruimte voor '\0'. Vergroot vóór het toevoegen van een teken als len + 1 >= cap. Bij capaciteit 8 passen zeven tekens terwijl de plaats voor het afsluitende nulteken vrij blijft; “hello world” leidt tot groei naar 16 en heeft twaalf bytes nodig inclusief het afsluitende nulteken.

Een opgeslagen pointer naar een element wordt ongeldig na een geslaagde vergroting. Bewaar een index en zoek het element opnieuw op met het nieuwe beginadres. Als je een array terugzet voor verder gebruik, geef je het blok vrij en stel je data = NULL; len = cap = 0; in.

Belangrijkste punten

  • De lengte telt gebruikte elementen; de capaciteit telt beschikbare plaatsen.
  • Vergroot vóór een toevoeging als de lengte gelijk is aan de capaciteit.
  • Werk de pointer en capaciteit pas bij nadat de wijziging van de grootte is geslaagd.
  • Meetkundige groei beperkt herhaald kopiëren ten koste van ongebruikte ruimte.
  • Beveilig berekeningen voor groei vóór de vermenigvuldiging tegen overloop.
  • Reserveer een extra byte voor het afsluitende nulteken van een string.
  • Bewaar indexen tijdens het vergroten en bereken pointers naar elementen daarna opnieuw.
4

Herken en voorkom geheugenfouten

Geheugenfouten ontstaan wanneer regels over eigenaarschap, levensduur, grenzen of initialisatie worden geschonden. Elke fout laat een ander spoor achter: een verloren adres, toegang na vrijgave, een index buiten het blok of een waarde die te vroeg wordt gelezen. Sommige fouten laten het programma direct crashen, terwijl andere gegevens beschadigen en later aan het licht komen. Door de code te lezen in termen van blokken en hun geschiedenis kun je de oorzaak achter het symptoom herkennen.

4.1 Het pad opsporen waar geheugen verloren gaat

Een geheugenlek is toegewezen opslag die een programma niet meer nodig heeft, maar nooit vrijgeeft. Vaak verdwijnt de laatste pointer, waardoor een latere free onmogelijk wordt. Een geheugenlek kan onopgemerkt blijven terwijl het geheugengebruik groeit.

Volg adressen, geen variabelenamen
char *p = malloc(100);
if (p == NULL) return EXIT_FAILURE;
p = malloc(200);  /* loses the only address of the first block */
if (p == NULL) return EXIT_FAILURE;
free(p);

De laatste free geeft alleen het tweede blok vrij. De naam p is hergebruikt; de eerste toegewezen opslag niet. Als de tweede aanvraag mislukt, is het blok van 100 bytes nog steeds verloren. Geef het eerste blok vrij voordat je het vervangt, wanneer het niet meer nodig is, of bewaar het adres in een andere variabele die het blok beheert.

Als je een pointer die eigenaar is op NULL zet voordat je het blok vrijgeeft, ontstaat hetzelfde verlies. De toewijzing verandert een pointervariabele; die geeft de allocator geen opdracht om iets vrij te geven. Automatische lokale variabelen verdwijnen aan het einde van een aanroep, maar de toegewezen blokken waarnaar ze wezen blijven bestaan.

Bekijk elke manier waarop een functie kan eindigen

Een vroege terugkeer kan het opruimen overslaan:

char *line = malloc(100);
if (line == NULL) return EXIT_FAILURE;
if (input_is_bad) {
    free(line);
    return EXIT_FAILURE;
}
/* Use line on the successful path. */
free(line);

Beide paden geven het blok vrij. In een functie met meer hulpbronnen vermindert één opruimsectie het aantal te controleren paden. Een functie die een toegewezen resultaat teruggeeft, draagt de verantwoordelijkheid van de eigenaar over; een functie die slechts een lokale pointer die eigenaar is achterlaat, veroorzaakt een geheugenlek.

Directe toewijzing van een mislukt realloc-resultaat is een andere manier om geheugen te verliezen. Geneste blokken lekken wanneer hun buitenste pointerarray wordt vrijgegeven voordat de binnenste adressen zijn gebruikt om op te ruimen.

Herhaling maakt kleine geheugenlekken kostbaar

Stel dat een lus per doorgang 100 bytes reserveert en die nooit vrijgeeft. Na 1.000 doorgangen heeft de lus 100.000 bytes achtergelaten. Geef elke buffer aan het einde van de doorgang vrij, of reserveer één keer vóór de lus geheugen, gebruik het blok opnieuw en geef het daarna vrij. Hergebruik voorkomt ook herhaald werk voor de allocator wanneer dezelfde capaciteit volstaat.

Een dienst die 64 bytes per verzoek lekt, verliest na 10.000 verzoeken 640.000 bytes, precies 625 KiB. Na een miljoen verzoeken is 64.000.000 bytes verloren, ongeveer 61 MiB. Programma’s die lang draaien kunnen uiteindelijk geen geheugen meer reserveren of het systeem vertragen. Het besturingssysteem vordert procesgeheugen normaal gesproken terug bij het afsluiten, maar dat helpt de dienst niet zolang die draait.

Verloren en nog bereikbaar zijn verschillende meldingen

Als een globale pointer bij het afsluiten nog het beginadres van een toegewezen blok bevat, noemt Valgrind het blok “still reachable”. Het adres is niet verloren; het programma heeft het blok alleen niet vrijgegeven. “Definitely lost” betekent dat er geen bruikbare pointer meer bestaat. Ruim bewust op, zodat deze rapporten onverklaarde geheugenallocaties duidelijk tonen.

Belangrijkste punten

  • Als je de laatste pointer die eigenaar is overschrijft of wist, kan een blok lekken.
  • Zowel normale paden als paden met een vroege terugkeer of fout moeten opruimen.
  • Herhaalde kleine geheugenlekken stapelen zich op terwijl het programma draait.
  • Door een geschikte buffer opnieuw te gebruiken, kun je zowel herhaald allocatiewerk als geheugenlekken per iteratie voorkomen.

4.2 Wanneer een pointer langer bestaat dan zijn doel

Een pointer wordt ongeldig wanneer de levensduur eindigt van het object waarnaar die wijst. De variabele kan nog bestaan, maar via die pointer het oude object lezen of beschrijven is ongeldig. Zowel het teruggeven van een lokaal adres als het vrijgeven van een toegewezen blok kan dit probleem veroorzaken.

Zoek het laatste gebruik vóór de vrijgave
int *scores = malloc(3 * sizeof *scores);
if (scores == NULL) return EXIT_FAILURE;
scores[0] = 90;
scores[1] = 75;
scores[2] = 82;
free(scores);
printf("%d\n", scores[0]);  /* use-after-free */

De toewijzing aan scores[0] gebeurde tijdens de levensduur van het blok. De laatste leesbewerking niet. Verplaats free(scores) naar na het afdrukken, zodra elk gebruik is afgerond. Alleen scores = NULL toewijzen vóór het afdrukken verandert de ongeldige toegang in een dereferentie van een nulpointer; het levert de ontbrekende gegevens niet op.

Dit is ongedefinieerd gedrag: C stelt geen eisen aan het resultaat. Het programma kan een oude waarde tonen, crashen of ongemerkt gegevens beschadigen. Vrijgegeven bytes blijven vaak op hun plek totdat de allocator ze opnieuw gebruikt. Een aannemelijk resultaat kan een leesbewerking na vrijgave daarom niet geldig maken.

Aliassen delen het levensduurprobleem
int *a = malloc(3 * sizeof *a);
if (a == NULL) return EXIT_FAILURE;
int *b = a;
free(a);
a = NULL;
b[0] = 1;  /* b also lost its live target */

Er was één blok, met twee pointervariabelen die het adres bevatten. Als je het blok via a vrijgeeft, eindigt de levensduur voor beide pointers. a op nul zetten wijst alleen aan a een waarde toe; het kan b niet bijwerken. Code die geleende aliassen gebruikt, moet klaar zijn voordat de eigenaar het blok vrijgeeft. Daarna mogen die aliassen niet meer worden gebruikt.

Dubbele vrijgave gebruikt ook een beëindigde levensduur

free twee keer voor één blok aanroepen is dubbele vrijgave, ook als de tweede aanroep via een alias gebeurt. Dit kan de administratie van de allocator beschadigen of een latere geheugenallocatie laten mislukken. Sommige bibliotheken detecteren dit direct en stoppen het programma, maar directe detectie is niet gegarandeerd.

Een variabele die eigenaar is na vrijgave op NULL zetten helpt bij een herhaalde opruimaanroep, omdat free(NULL) geldig is. Het maakt free(b) niet veilig als b nog naar het voormalige blok verwijst. Precies één eigenaar moet bepalen wanneer het blok wordt vrijgegeven.

Ook een wijziging van de grootte beëindigt de oude levensduur

Een geslaagde realloc vervangt het oude object, ook als het teruggegeven numerieke adres onveranderd is. Opgeslagen pointers naar posities in het oude blok moeten opnieuw worden berekend vanuit de teruggegeven pointer. Bewaar posities als indexen zolang groei mogelijk is. Een mislukte realloc met een grootte groter dan nul laat het oude toegewezen blok daarentegen bestaan.

Een functie die het adres van een automatische lokale variabele teruggeeft, veroorzaakt een vergelijkbare levensduurfout zonder een aanroep van free: het object houdt op te bestaan wanneer de functie terugkeert. Gebruik opslag van de aanroepende functie of een resultaat op de heap met duidelijk eigenaarschap. Gebruik na vrijgave en dubbele vrijgave kunnen ook beveiligingsfouten worden wanneer een aanvaller de hergebruikte opslag kan beïnvloeden.

Belangrijkste punten

  • Een ongeldige pointer naar een niet meer bestaand object wijst naar een object waarvan de levensduur is geëindigd.
  • Rond elke lees- en schrijfbewerking af voordat je het blok vrijgeeft.
  • Eén vrijgave maakt elke alias naar het oude object ongeldig.
  • Een pointer die eigenaar is op nul zetten helpt bij herhaald opruimen, maar herstelt geleende aliassen niet.
  • Dubbele vrijgave kan de administratie van de allocator beschadigen.
  • Een geslaagde realloc maakt opgeslagen pointers naar posities in het oude blok ongeldig.

4.3 Geldige grenzen en geïnitialiseerde waarden

Voor geldige toegang tot een element is zowel voldoende opslag nodig als een waarde die is ingesteld voordat die wordt gelezen. Een blok kan de juiste grootte hebben maar niet-geïnitialiseerde gegevens bevatten, of bruikbare waarden bevatten maar buiten zijn grenzen worden benaderd.

Controleer het bereik van de elementen

Voor n elementen lopen de geldige indexen van nul tot en met n - 1. Een lus met i <= n voert één extra toegang uit. Bij tien ints van 4 bytes is het toegewezen blok 40 bytes; p[10] begint direct na die bytes. Gebruik i < n om de tien elementen te doorlopen.

Ook een index uit invoer moet vóór gebruik op zijn bereik worden gecontroleerd. Invoer met teken moet niet-negatief en kleiner dan het aantal elementen zijn. Houd het aantal bij de pointer en geef beide door aan functies. Een pointer alleen kan het toegestane bereik niet beschrijven.

Houd bij strings rekening met het afsluitende nulteken
char *copy = malloc(strlen(s));
strcpy(copy, s);  /* writes the terminator beyond the allocation */

Dit onjuiste fragment heeft ook een foutcontrole nodig. De grootte herstel je met malloc(strlen(s) + 1), gevolgd door de controle en daarna de kopieerbewerking. “Amsterdam” heeft negen tekens, maar heeft tien bytes nodig met '\0'. Het afsluitende nulteken is een daadwerkelijk opgeslagen byte, ook al wordt het niet als onderdeel van de tekst afgedrukt.

Een pointerarray is geen opslag voor strings
char **names = malloc(n * sizeof *names);

Dit reserveert n plaatsen voor pointers. Het maakt geen tekenbuffers waarnaar die pointers kunnen wijzen. Wijs aan names[i] een geldig stringadres toe voordat je de pointer als string gebruikt. De pointer kan een bestaande array of een literal met een voldoende lange levensduur lenen, of eigenaar zijn van een afzonderlijk toegewezen stringkopie. Bij het opruimen van de geneste blokken worden alleen de strings vrijgegeven waarvan het programma eigenaar is.

Dezelfde redenering over grootte geldt voor numerieke arrays: malloc(n) telt bytes en sizeof p meet een pointer. Gebruik n * sizeof *p, met de overloopcontrole en de controle op een nulpointer, om een aantal elementen correct uit te drukken.

Schrijf vóór het lezen
int *t = malloc(3 * sizeof *t);
if (t == NULL) return EXIT_FAILURE;
t[0] = 5;
t[1] = 7;
printf("%d\n", t[0] + t[1] + t[2]);  /* t[2] was never set */
free(t);

t[2] ligt binnen het toegewezen blok, maar de waarde is niet geïnitialiseerd. Wijs vóór de som een waarde toe, of gebruik calloc als nul de bedoelde beginwaarde is. De bytes die bij groei met realloc worden toegevoegd, vereisen dezelfde aandacht. Binnen de grenzen blijven initialiseert het geheugen niet.

Waarom de crash later kan optreden

Een schrijfbewerking buiten de grenzen kan nabijgelegen gegevens of de administratie van de allocator beschadigen. Een leesbewerking buiten de grenzen kan niet-gerelateerde gegevens blootleggen. Beide zijn ongedefinieerd gedrag. Een kleine grensoverschrijding kan toegewezen geheugen raken en aan directe detectie ontsnappen; een latere free kan de eerste zichtbare fout zijn.

Controleer de grootte van de geheugenallocatie, het indexbereik en de initialisatiegeschiedenis afzonderlijk. Een groter toegewezen blok kan geen betekenisvolle waarde leveren aan een niet-geïnitialiseerd element en op nul zetten kan een index buiten het bereik niet geldig maken.

Belangrijkste punten

  • Geldige indexen voor n elementen lopen van nul tot en met n - 1.
  • Opslag voor een string bevat één byte voor het afsluitende nulteken.
  • Een array van stringpointers reserveert geen geheugen voor de strings zelf.
  • Initialiseer alle waarden die een berekening leest, inclusief nieuw toegevoegde elementen.
  • Een ongeldige schrijfbewerking kan gegevens al lang vóór een zichtbare crash beschadigen.

4.4 Een crash tot de oorzaak herleiden

Een segmentatiefout is een reactie van het besturingssysteem op verboden geheugentoegang. Veelvoorkomende oorzaken zijn het derefereren van een nulpointer of niet-geïnitialiseerde pointer, schrijven naar een stringliteral, toegang ver buiten een array, gebruik van vrijgegeven opslag en het uitputten van de stapel.

Wat de melding je vertelt

Op Linux stopt SIGSEGV meestal het proces en kan een shell “Segmentation fault (core dumped)” afdrukken. Een veelvoorkomende afsluitstatus van de shell is 139, volgens de signaalconventie van 128 plus signaal 11. macOS kan bij ongeldige toegang SIGSEGV of SIGBUS melden. Windows meldt de vergelijkbare fout als een toegangsschending, vaak met uitzonderingscode 0xC0000005.

Deze meldingen wijzen op een mislukte toegang, niet op de volledige voorgeschiedenis. Een kleine grensoverschrijding kan al vroeg een pointer beschadigen en veel later een crash veroorzaken. Veel ongeldige geheugentoegangen blijven binnen geheugen dat het proces kan adresseren, zodat ze ongemerkt gegevens beschadigen in plaats van een foutmelding te veroorzaken.

Begin met regelinformatie

Bouw een uitvoerbaar debugbestand met waarschuwingen:

cc -std=c17 -g -O0 -Wall -Wextra program.c -o program

Voer het daarna uit onder een debugger of hulpmiddel voor geheugencontrole. Een aanroepoverzicht vermeldt de functieaanroepen die actief waren bij de crash. Zoek het bovenste frame dat bij je eigen code hoort. Bibliotheekfuncties zoals free kunnen de plek zijn waar een ongeldige toestand zichtbaar wordt, terwijl een eerdere aanroep in je programma die toestand veroorzaakte.

Zoek op de gemelde regel elke pointer die wordt gederefereerd. Volg voor elke pointer de waarde terug naar de oorsprong. Is de geheugenallocatie gecontroleerd? Bestaat het object waarnaar wordt gewezen nog? Past de index binnen het blok? Heeft de pointer überhaupt een geldig adres gekregen?

Onderzoek een nulresultaat
int *p = NULL;
printf("%d\n", *p);  /* invalid dereference */

Het afdrukken vereist een leesbewerking via p, maar op dat adres bestaat geen object met een geheel getal. Als p van een geheugenallocatiefunctie kwam, controleer dan het resultaat vóór deze regel en volg bij een fout een foutpad. Als de pointer na vrijgave is gewist, verplaats het benodigde gebruik dan naar vóór de vrijgave of verander waar de gegevens worden bewaard.

Aan een niet-geïnitialiseerde pointer, zoals in int *p; *p = 5;, is geen geldig doel toegewezen. Een geheugenallocatie, het adres van een nog bestaand object of een gecontroleerde geleende pointer moet dat doel vastleggen vóór het derefereren.

Volg de oorzaak wanneer symptomen verschuiven

Een eindeloos recursieve functie kan in een niet-geoptimaliseerde bouw de stackframes uitputten. Een stopvoorwaarde herstelt de logica. Een optimaliserende compiler kan sommige recursieve aanroepen echter omzetten in een lus, zodat het uitblijven van een stapelcrash de onbegrensde berekening niet nuttig maakt.

Afgedrukte voortgangsmeldingen zijn ook onvolledig bewijs. stdout wordt gebufferd: uitvoer kan ongeschreven blijven als een proces crasht, vooral bij omgeleide uitvoer. Gebruik voor een diagnostische melding stderr of roep fflush(stdout) aan na een voortgangsmelding. Als je een afdrukopdracht toevoegt, kan de geheugenindeling veranderen en kan een fout verborgen blijven; de ongeldige toegang is daarmee niet hersteld. Voer het programma opnieuw uit met een controlehulpmiddel om de onjuiste bewerking zelf te vinden.

Belangrijkste punten

  • Een segmentatiefout meldt een verboden toegang; de oorspronkelijke beschadiging kan eerder zijn ontstaan.
  • Bouw met debuginformatie en zoek het eerste relevante frame in je eigen code.
  • Volg de oorsprong van elke pointer, de levensduur van het object waarnaar die wijst en het indexbereik.
  • Gebufferde uitvoer kan een afdrukopdracht verbergen die vóór een crash is uitgevoerd.
  • Een veranderd symptoom na het toevoegen van een afdrukopdracht vereist nog steeds onderzoek.
5

Spoor geheugenfouten op met Valgrind en sanitizers

Hulpmiddelen voor geheugencontrole maken verborgen fouten zichtbaar door de toegang en de geschiedenis van het blok te melden. Valgrind observeert een uitvoerbaar debugbestand; AddressSanitizer voegt controles toe wanneer het programma wordt gebouwd. Leer hun meldingen te verbinden met elementgrenzen, allocatieplaatsen en vrijgaveplaatsen, en herstel de fout met behoud van correct eigenaarschap. Na een uitvoering zonder fouten volgen nieuwe invoerwaarden die andere paden doorlopen.

5.1 Een Valgrind-rapport lezen

Valgrinds standaardhulpmiddel Memcheck observeert een gecompileerd programma terwijl het draait. Het houdt heapgrenzen, levensduren van toegewezen blokken en de initialisatie van waarden bij. Het heeft geen bouw met een sanitizer nodig, maar debuginformatie maakt de rapporten veel gemakkelijker te gebruiken.

Bouwen en uitvoeren
cc -std=c17 -g -O0 -Wall -Wextra program.c -o program
valgrind --leak-check=full ./program

Plaats de eigen argumenten van het programma na ./program. Voeg --show-leak-kinds=all toe om alle geheugenlekcategorieën te tonen en --track-origins=yes wanneer de herkomst van een niet-geïnitialiseerde waarde onduidelijk is.

Valgrind draait op Linux en enkele andere Unix-achtige systemen, maar niet rechtstreeks op Windows of Macs met Apple-siliconchips. Memcheck vertraagt de uitvoering vaak ongeveer 20 tot 30 keer. Gebruik eerst een kleine invoer die de fout reproduceert en voer daarna verdere tests uit. Een test zonder fouten dekt alleen de paden die daadwerkelijk zijn uitgevoerd.

Lees toegangsgrootte en blokgrootte afzonderlijk

Een rapport met de kop “Invalid write of size 4” beschrijft een schrijfbewerking van vier bytes. Een adres “0 bytes after a block of size 40 alloc'd” begint direct na een toegewezen blok van 40 bytes. Als dat blok tien ints van 4 bytes bevat, begint de ongeldige toegang bij element 10; het laatste geldige element is 9.

Het rapport noemt ook de regel van de toegang en de aanroepstapel van de geheugenallocatie. Gebruik die samen: de geheugenallocatie vertelt je wat de bedoelde grenzen zijn en de toegang laat zien welke index die grenzen heeft overschreden. “Size 4” betekent niet dat het programma vier elementen voorbij de grens ging.

“Invalid read of size N” beschrijft op dezelfde manier een ongeldige leesbewerking. Een adres binnen een blok dat als “free'd” wordt beschreven, wijst op een levensduurprobleem; vergelijk de locaties van de geheugenallocatie, vrijgave en toegang. “Invalid free() / delete / delete[] / realloc()” kan wijzen op een dubbele vrijgave of een adres dat nooit het begin van een geldig toegewezen blok was.

Bij een voorwaarde die niet-ingestelde gegevens gebruikt, kan Memcheck “Conditional jump or move depends on uninitialised value(s)” melden. Die spelling hoort bij de melding van het hulpmiddel. De gemelde voorwaarde gebruikte de waarde; --track-origins=yes helpt achterhalen waar de niet-geïnitialiseerde opslag vandaan kwam.

Lees de geheugenlekcategorie
RapportcategorieBetekenis
Definitely lostEr bestaat geen pointer naar het blok meer
Indirectly lostHet blok is alleen via een ander verloren blok bereikbaar
Possibly lostEr is alleen een pointer naar een positie binnen het blok gevonden
Still reachableBij afsluiten bestaat nog een pointer, maar het blok is niet vrijgegeven

Stel dat een hulpfunctie geheugen voor tien ints van 4 bytes reserveert in een lokale pointer en terugkeert zonder het geheugen vrij te geven of het adres terug te geven. Het blok van 40 bytes is zeker verloren. Als een globale pointer het beginadres bewaart, zou een niet-vrijgegeven blok daarentegen nog bereikbaar zijn. De totalen voor geheugenallocaties en vrijgaven in het heapoverzicht beschrijven gebeurtenissen, inclusief wijzigingen van de grootte; ze vervangen het redeneren over eigenaarschap niet.

Een afsluiting zonder fouten kan “All heap blocks were freed -- no leaks are possible” en “ERROR SUMMARY: 0 errors from 0 contexts” melden. Memcheck vindt doorgaans niet elke overschrijding van een lokale of globale array die binnen verder geldig geheugen blijft. Herstel de eerste gemelde fout, voer opnieuw uit en test meer invoerwaarden, waaronder lege invoer en paden die vroeg terugkeren.

Belangrijkste punten

  • Gebruik debuginformatie zodat Memcheck je bronregels kan aanwijzen.
  • Lees de toegangsbreedte, blokgrenzen en allocatiegeschiedenis in samenhang.
  • Gebruik --track-origins=yes om de herkomst van niet-geïnitialiseerde waarden te achterhalen.
  • Onderscheid verloren blokken van blokken die bij afsluiten nog bereikbaar zijn.
  • Herstel de eerste fout voordat je latere gevolgen interpreteert.
  • Voer meerdere invoerwaarden uit, omdat een controlehulpmiddel alleen uitgevoerde paden observeert.

5.2 Controles in het programma inbouwen

AddressSanitizer, of ASan, voegt tijdens het compileren controles op geheugentoegang toe. Het resulterende uitvoerbare bestand controleert zichzelf tijdens de uitvoering. Dit is nuttig voor gewone ontwikkelingstests, omdat de vertraging doorgaans ongeveer een factor twee is, veel minder dan bij Memcheck.

Compileer en koppel met de sanitizer
cc -std=c17 -g -fsanitize=address -fno-omit-frame-pointer \
    program.c -o program-asan
./program-asan

Gebruik de sanitizervlag zowel bij het compileren als bij het definitieve koppelen als je in afzonderlijke stappen bouwt. -g levert regelinformatie en -fno-omit-frame-pointer helpt bruikbare aanroepstapels te behouden. De compiler van Microsoft gebruikt /fsanitize=address.

ASan detecteert grensoverschrijdingen op de heap, op de stapel en in globale arrays, gebruik na vrijgave en ongeldige en dubbele vrijgaven. Controles op de levensduur binnen het geldigheidsgebied en optionele controles op gebruik na terugkeer kunnen ook pointers naar voormalige lokale variabelen vinden. ASan vereist een nieuwe bouw en extra geheugen. Houd deze instrumentatie in ontwikkel- en testversies.

Lees de drie geschiedenissen

Een rapport over gebruik na vrijgave kan beginnen met “ERROR: AddressSanitizer: heap-use-after-free”. Een daaropvolgende “READ of size 4” beschrijft de ongeldige bewerking. Het rapport toont daarna waar het geheugen is “freed by thread T0 here:” en “previously allocated by thread T0 here:”.

Begin in elke aanroepstapel bij de eerste relevante regel in je code. De geheugenallocatie maakt het blok aan; de vrijgave beëindigt de levensduur; de gemelde leesbewerking vindt na dat einde plaats. Bepaal of je de vrijgave naar na de leesbewerking verplaatst of stopt met toegang tot het blok na vrijgave. Het rapport levert de geschiedenis, terwijl het bedoelde eigenaarschap de oplossing bepaalt.

Bij “heap-buffer-overflow” begint een adres “located 0 bytes after 20-byte region” direct voorbij het gebied. Reken bytes pas om naar elementen nadat je de door het programma gebruikte typegrootte hebt gecontroleerd. ASan stopt normaal bij de eerste fout, dus herstel kan bij de volgende uitvoering een andere fout aan het licht brengen.

Geheugenlekcontrole en niet-geïnitialiseerde waarden zijn verschillend

LeakSanitizer controleert bij afsluiten op niet-vrijgegeven toegewezen blokken. Op Linux is dit normaal ingeschakeld met ASan. Bij ondersteunde macOS-toolchains kun je dit inschakelen met ASAN_OPTIONS=detect_leaks=1; sommige Mac-toolchains bieden het niet. Apples afzonderlijke hulpmiddel leaks is daar een andere mogelijkheid om geheugenlekken te controleren.

ASan houdt niet bij of een waarde binnen de grenzen is geïnitialiseerd. Gebruik voor dat probleem Memcheck of Clangs MemorySanitizer op een ondersteunde Linux-configuratie. UndefinedBehaviorSanitizer controleert andere soorten ongedefinieerd gedrag, zoals overloop bij gehele getallen met teken, en kan met ASan worden gecombineerd met -fsanitize=address,undefined.

Kies voor elk controlehulpmiddel een afzonderlijke uitvoering
BehoefteBruikbaar hulpmiddel
Snelle controles tijdens ontwikkeling, inclusief grenzen van lokale arraysBouw met ASan
Herkomst van niet-geïnitialiseerde waarden volgen zonder sanitizerinstrumentatieMemcheck op een ondersteund systeem
Geheugenlekken bij afsluitenLeakSanitizer of Memcheck

Voer een uitvoerbaar ASan-bestand niet uit onder Valgrind. Bouw voor Memcheck een afzonderlijk gewoon uitvoerbaar debugbestand en combineer ASan en MemorySanitizer niet in één bouw. Varieer na een uitvoering zonder fouten de invoer, zodat andere paden voor geheugenallocatie, groei en opruimen worden uitgevoerd.

Belangrijkste punten

  • ASan vereist compilatie en koppeling met zijn instrumentatievlag.
  • Lees de aanroepstapels van de ongeldige toegang, vrijgave en geheugenallocatie om een levensduurfout te reconstrueren.
  • LeakSanitizer controleert bij afsluiten op geheugenlekken waar het platform dit ondersteunt.
  • ASan detecteert het gewone lezen van niet-geïnitialiseerde waarden niet.
  • Gebruik afzonderlijke uitvoeringen en geschikte bouwversies voor ASan en Memcheck.

5.3 Van rapport naar gecontroleerde oplossing

Een geheugenrapport beschrijft een waargenomen fout en delen van de voorgeschiedenis. Een bruikbare oplossing herstelt de juiste grootte, grenzen, initialisatie en het juiste eigenaarschap en controleert daarna het gecorrigeerde programma opnieuw.

Beperk en reproduceer

Zoek de kleinste invoer die de fout nog veroorzaakt. Bouw met -g, waarschuwingen en weinig optimalisatie en gebruik daarna Memcheck of een bouw met ASan. Lees het eerste rapport voordat je latere rapporten onderzoekt; een beschadigde toestand kan een reeks misleidende fouten veroorzaken.

Stel dat een programma het volgende bevat:

size_t n = 5;
int *v = malloc(n * sizeof *v);
for (size_t i = 0; i <= n; ++i) v[i] = (int)(i * i);
printf("%d\n", v[2]);
return 0;

Op een systeem met ints van 4 bytes meldt ASan een schrijfbewerking van vier bytes op de eerste byte na een toegewezen blok van 20 bytes. De schrijfbewerking staat in de lus. Memcheck kan ook de grensoverschrijding melden en later een niet-vrijgegeven blok; of het blok verloren of nog bereikbaar is, hangt af van welke pointers bij afsluiten nog bestaan.

Koppel het rapport aan de code

Vijf ints bezetten de indexen nul tot en met vier. De voorwaarde i <= n bereikt index vijf. De grootte van het toegewezen blok is correct; de lusgrens niet. Het blok groter maken zou het symptoom verbergen terwijl de doorloop verkeerd blijft.

Ook ontbreekt een controle van de geheugenallocatie en wordt het blok waarvan het programma eigenaar is niet vrijgegeven. Die problemen vereisen hun eigen oplossingen, ook als het eerste rapport het programma stopte voordat het programma die punten bereikte.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t n = 5;
    int *v = malloc(n * sizeof *v);
    if (v == NULL) return EXIT_FAILURE;
    for (size_t i = 0; i < n; ++i) v[i] = (int)(i * i);
    printf("%d\n", v[2]);
    free(v);
    return EXIT_SUCCESS;
}

Het herstelde programma initialiseert vijf geldige elementen, drukt 4 af terwijl het blok bestaat en geeft het vrij. Het vaste kleine aantal past; een aantal uit invoer vereist vóór de geheugenallocatie ook een controle op overloop bij de berekening van de grootte.

Gebruik de geschiedenis in het rapport voor het juiste herstel

Vergelijk bij gebruik na vrijgave de aanroepstapels van de vrijgave en de toegang om het laatste geldige gebruik te vinden. Verplaats de vrijgave naar daarna of verwijder de toegang via de verouderde pointer. Een pointer op NULL zetten kan de vroegere gegevens niet opnieuw beschikbaar maken.

Bij een geheugenlek vertelt de aanroepstapel van de geheugenallocatie waar het blok ontstond. Die wijst niet de juiste regel voor vrijgave aan. Volg het eigenaarschap langs geslaagde terugkeer en fouten om te vinden waar het laatste gebruik eindigt. Verwijder bij dubbele vrijgave de dubbele vrijgave of verbeter tegenstrijdig eigenaarschap. De enige geldige vrijgave verwijderen veroorzaakt een geheugenlek.

Voeg bij niet-geïnitialiseerde gegevens --track-origins=yes van Memcheck toe. De regel die de waarde gebruikt kan ver verwijderd zijn van de geheugenallocatie die niet-ingestelde bytes leverde. Initialiseer elke waarde die de berekening leest, of gebruik calloc als nul de juiste begintoestand is.

Controleer het herstel op meer dan één pad

Bouw opnieuw en voer de oorspronkelijke invoer die de fout reproduceert opnieuw uit. Ga door totdat het rapport geen fouten meer bevat en probeer daarna lege invoer, groeigrenzen, gewone invoer en terugkeer bij fouten. Compilerwaarschuwingen vinden sommige problemen vóór de uitvoering; hulpmiddelen voor uitvoeringstijd controleren de paden die deze tests bereiken.

Belangrijkste punten

  • Reproduceer met een kleine invoer en lees de eerste fout.
  • Gebruik de blokgrootte en toegangspositie om de werkelijke grensfout te vinden.
  • Controleer of geheugenallocatie is mislukt en of er wordt opgeruimd, ook als de eerste toegangsfout de uitvoering stopt.
  • Geheugenlekrapporten tonen allocatieplaatsen; eigenaarschap bepaalt de vrijgaveplaatsen.
  • Bouw na herstel opnieuw, voer de invoer die de fout reproduceerde opnieuw uit en doorloop extra paden.

Dynamisch geheugenbeheer in C: voorbeelden

5 Walkthroughs

1

Volg een object door functieaanroepen

9 stappen
Volg lokale, statische en toegewezen gegevens en kies daarna opslag voor een teruggegeven resultaat.
  1. 1
    IntroductieEen adres heeft een bestaand object nodig

    Met een pointer kan een deel van een programma gegevens vinden die elders zijn gemaakt. Of de pointer bruikbaar is, hangt ervan af of die gegevens nog bestaan wanneer de pointer wordt gebruikt.

    Volg een hulpfunctie die een array van gehele getallen maakt, het adres teruggeeft en de aanroepende functie het resultaat laat gebruiken. Houd de lokale pointer en de array bij het volgen van de aanroep uit elkaar: ze hoeven niet dezelfde levensduur te hebben.

  2. 2
    UitlegBenoem de drie levensduren

    Een gewone lokale variabele heeft automatische opslagduur en houdt op te bestaan met haar blok. Een globale variabele of lokale variabele met static bestaat zolang het programma draait. Een toegewezen blok bestaat tot de vrijgave.

    Op gangbare systemen staan automatische objecten op de aanroepstapel en gebruiken toegewezen blokken de heap. Deze namen beschrijven gebruikelijke implementaties; de regels van C bepalen de levensduren ongeacht de geheugenindeling.

  3. 3
    VoorbeeldMaak de lokale pointer en het blok

    Binnen make_array maakt int *p = malloc(5 * sizeof *p); twee afzonderlijke objecten: de lokale pointer en, bij succes, een toegewezen blok voor vijf ints.

    Als een int 4 bytes is, bevat het blok 20 bytes. Controleer p vóór gebruik. De pointervariabele hoort bij de functieaanroep, maar het toegewezen blok maakt geen deel uit van de automatische opslag van die aanroep.

  4. 4
    VoorbeeldGeef het adres terug

    De hulpfunctie geeft p terug en de aanroepende functie slaat het resultaat op in int *nums. Het adres wordt gekopieerd; de array niet. Daarna houden de lokale variabelen van de hulpfunctie op te bestaan.

    Bij succes verwijst nums nog steeds naar de toegewezen array. De aanroepende functie controleert het resultaat, initialiseert en gebruikt de elementen en geeft het blok uiteindelijk vrij. De terugkeer heeft het blok niet vrijgegeven, omdat het toegewezen opslagduur heeft.

  5. 5
    UitdagingWat blijft bestaan na de terugkeer?

    Een hulpfunctie maakt een automatische int count en een afzonderlijke array op de heap. De functie geeft het adres van de array terug. Welk van deze twee objecten kan de aanroepende functie nog gebruiken?

    ---reveal---

    De array op de heap blijft bestaan totdat die wordt vrijgegeven. De automatische count van de hulpfunctie hield op te bestaan bij de terugkeer. Het gekopieerde adres verwijst alleen naar de array; een adres bewaren zou count niet behouden als de hulpfunctie in plaats daarvan &count had teruggegeven.

  6. 6
    VoorbeeldProbeer een lokale array terug te geven

    Verander de hulpfunctie nu zodat die int values[5]; gebruikt en values teruggeeft. Die array heeft automatische opslagduur. De array houdt op te bestaan als de hulpfunctie terugkeert, waardoor de aanroepende functie geen bestaand object heeft om te gebruiken.

    De grootte is nog steeds vijf ints, maar de levensduur is anders. Een bruikbaar resultaat kan in plaats daarvan worden geplaatst in een buffer die de aanroepende functie beschikbaar stelt, of worden toegewezen en aan die functie worden overgedragen met een duidelijke verantwoordelijkheid voor het opruimen.

  7. 7
    VerbandKies een afspraak voor de buffer

    Een statische resultaatbuffer blijft na terugkeer bestaan, maar elke aanroep deelt die. Een latere aanroep kan een eerder resultaat overschrijven. Een resultaat op de heap kan elke aanroep een eigen onafhankelijke opslag geven, waarbij de aanroepende functie verantwoordelijk is voor de vrijgave.

    Voor tijdelijk werk is een kleine vaste lokale array eenvoudiger. Arrays waarvan de grootte afhangt van invoer, zeer grote arrays of uitbreidbare arrays vragen om gecontroleerde geheugenallocatie, zodat het programma fouten kan afhandelen en de levensduur kan bepalen.

  8. 8
    UitdagingTwee resultaten moeten onafhankelijk blijven

    Een aanroepende functie heeft twee beschrijfbare resultaten van opeenvolgende aanroepen nodig en moet beide bewaren. Ze levert geen buffer aan. Zou één statische resultaatarray voldoen? Welk ontwerp past?

    ---reveal---

    Eén statische array zou worden gedeeld en overschreven. Geef bij elke aanroep een afzonderlijk toegewezen resultaat terug, controleer elk resultaat en laat de aanroepende functie beide na hun laatste gebruik vrijgeven. De levensduur op de heap zorgt dat ze blijven bestaan; afzonderlijke blokken zorgen voor onafhankelijkheid.

  9. 9
    SamenvattingHoud het adres en de levensduur uit elkaar

    Volg het object waarnaar een pointer wijst: automatische lokale variabelen houden op te bestaan met hun blok, statische objecten bestaan tijdens de uitvoering en toegewezen blokken moeten bewust worden vrijgegeven.

    Vraag je bij een teruggegeven resultaat af of de opslag blijft bestaan en of aanroepende functies onafhankelijke kopieën nodig hebben. Vraag je bij tijdelijk werk af of een kleine vaste lokale array al voldoet. De vereiste grootte en levensduur bepalen het ontwerp voordat je een geheugenallocatieaanroep schrijft.

2

Reserveer geheugen voor een raster en ruim op elk pad op

13 stappen
Werk van elementgroottes naar gecontroleerde aanvragen, geïnitialiseerde waarden en opruimen na gedeeltelijk succes.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieHoud rekening met succes en falen
  2. 2
    UitlegZet aantallen om naar bytes
  3. 3
    TipControleer vóór de vermenigvuldiging
  4. 4
    UitdagingAantal of pointergrootte?
  5. 5
    UitlegGeef tellers een beginwaarde
  6. 6
    VoorbeeldGebruik de rij die op nul is gezet
  7. 7
    UitdagingHoe zit het met latere groei?
  8. 8
    UitlegTel blokken, geen pointernamen
  9. 9
    VoorbeeldLaat een gedeeltelijke fout de opbouw stoppen
  10. 10
    UitdagingKies de volgorde van opruimen
  11. 11
    KernpuntEen eigenaar kan uitlenen
  12. 12
    UitdagingEén pad stopt vroeg
  13. 13
    SamenvattingHoud opruimen en opbouwen bij elkaar
3

Voeg waarden toe zonder gegevens te verliezen

12 stappen
Volg de lengte, capaciteit en fouten bij het wijzigen van de grootte van een dynamische array, inclusief de situatie met het afsluitende nulteken van een string.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVergroot vóór de volgende opslag
  2. 2
    UitlegEen wijziging van de grootte kan het object vervangen
  3. 3
    VoorbeeldHoud falen en succes uit elkaar
  4. 4
    UitdagingWat blijft behouden na verkleinen?
  5. 5
    UitlegLengte en capaciteit beantwoorden verschillende vragen
  6. 6
    VoorbeeldPasseer de eerste groeigrens
  7. 7
    VoorbeeldPasseer de volgende grens
  8. 8
    KernpuntExtra ruimte zorgt voor minder kopieën
  9. 9
    UitdagingWelke toestand blijft na een mislukte toevoeging?
  10. 10
    VerbandEen string reserveert een plaats voor het afsluitende nulteken
  11. 11
    UitdagingEen geleende selectie behouden bij groei
  12. 12
    SamenvattingWerk de toestand pas na succes bij
4

Onderzoek de hele geschiedenis van een blok

15 stappen
Onderscheid geheugenlekken, ongeldige aliassen, grensfouten en niet-ingestelde waarden en herleid daarna een crash tot de oorzaak.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVier vragen bij elke toegang
  2. 2
    VoorbeeldVerlies een toegewezen blok door het adres te vervangen
  3. 3
    UitdagingRuimt het wissen van de pointer op?
  4. 4
    TipOnderzoek zowel uitgangen als lussen
  5. 5
    VoorbeeldGeef één keer vrij en maak alle aliassen ongeldig
  6. 6
    UitdagingHerstel het laatste gebruik
  7. 7
    KernpuntVerwachte uitvoer kan uit ongeldige code komen
  8. 8
    VoorbeeldTel het afsluitende nulteken mee
  9. 9
    VoorbeeldToegewezen betekent niet geïnitialiseerd
  10. 10
    UitdagingOnderscheid twee grenzen
  11. 11
    UitlegEen fout is een symptoom
  12. 12
    VoorbeeldLees de toegang die crasht
  13. 13
    UitdagingEen afdrukopdracht laat de crash verdwijnen
  14. 14
    UitdagingCombineer eigenaarschap en grenzen
  15. 15
    SamenvattingBepaal welke regel is geschonden
5

Lees een rapport en controleer de oplossing

14 stappen
Kies een controlehulpmiddel, interpreteer bytes en aanroepstapels en herstel een grensoverschrijding en ontbrekende opruiming.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieMaak van een melding een blokgeschiedenis
  2. 2
    UitlegGebruik een gewone debugversie voor Memcheck
  3. 3
    VoorbeeldInterpreteer de adresbeschrijving
  4. 4
    UitdagingLees bytes als bytes
  5. 5
    TipKies een rapportoptie voor het probleem
  6. 6
    UitlegBouw afzonderlijk een uitvoerbaar ASan-bestand
  7. 7
    VoorbeeldVolg gebruik na vrijgave door drie aanroepstapels
  8. 8
    UitdagingEen ASan-uitvoering zonder fouten mist een waardefout
  9. 9
    VoorbeeldKoppel het rapport aan de lus
  10. 10
    VoorbeeldZorg dat de gegevens tijdens het werk bestaan
  11. 11
    UitdagingWaar moet een geheugenlek worden hersteld?
  12. 12
    VerbandControleer afsluiting en platformondersteuning
  13. 13
    UitdagingRond de herstelcyclus af
  14. 14
    SamenvattingGebruik het bewijs, herstel en voer opnieuw uit

Dynamisch geheugenbeheer in C: oefenvragen

50 Oefenvragen in 5 sets

Set 1: Levensduren, byteaantallen en eerste controles10 Oefenvragen

Vraag 1 van 10

Een buffer bevat zes geïnitialiseerde ints. Code bewaart index 4, verkleint het blok met succes tot drie ints en wil daarna het eerder geselecteerde element gebruiken. Wat is een geldige volgende stap?
  1. Bereken de geselecteerde pointer opnieuw als &data[4].
  2. Wijs de selectie af omdat index 4 is verwijderd.
  3. Lees de oude geselecteerde pointer vóór een volgende geheugenallocatie.
  4. Lees index 4 als het adres na verkleining gelijk bleef.
Toon antwoordVerberg antwoord
Antwoord: B
Bij verkleining blijven alleen de eerste drie elementen behouden, op indexen nul tot en met twee. Index 4 duidt geen element in het verkleinde blok meer aan en kan dus niet worden hersteld door een pointer opnieuw te berekenen. Bij succes eindigde ook de levensduur van het oude object, zelfs als het adres gelijk bleef; opgeslagen pointers kunnen verwijderde gegevens niet redden.

Vraag 2 van 10

Een functie moet een beschrijfbaar tekstresultaat maken dat na haar terugkeer blijft bestaan. De aanroepende functie levert geen buffer aan en afzonderlijke aanroepen moeten onafhankelijke resultaten behouden. Welk opslagontwerp past?
  1. Geef een lokale array terug; de aanroepende functie bewaart het adres.
  2. Geef één statische buffer terug; de aanroepende functie geeft die vrij.
  3. Geef de literal terug; de aanroepende functie wijzigt de letters.
  4. Geef een toegewezen kopie terug; de aanroepende functie geeft die vrij.
Toon antwoordVerberg antwoord
Antwoord: D
Een toegewezen kopie bestaat langer dan de aanroep die die kopie maakt en geeft elk resultaat onafhankelijke opslag. Een statische buffer blijft ook bestaan, maar deelt één resultaat tussen aanroepen en kan niet door de aanroepende functie worden vrijgegeven. Een lokale array houdt op te bestaan bij terugkeer en een stringliteral biedt geen beschrijfbare opslag.

Vraag 3 van 10

Een array heeft 13 ints nodig. Op dit systeem is sizeof(int) gelijk aan 4. Hoeveel bytes moet de geheugenallocatie aanvragen?
  1. 52 bytes
  2. 13 bytes
  3. 17 bytes
  4. 104 bytes
Toon antwoordVerberg antwoord
Antwoord: A
De aanvraag heeft 13 elementen van elk 4 bytes nodig, dus 52 bytes. Een aanvraag van 13 bytes verwart het aantal elementen met het aantal bytes; daarin passen op dit systeem geen dertien ints.

Vraag 4 van 10

Wat is hits[4] na een geslaagde int *hits = calloc(6, sizeof *hits);, vóór latere toewijzingen?
  1. Een niet-geïnitialiseerde waarde
  2. Het gehele getal 6
  3. 0
  4. Het gehele getal 4
Toon antwoordVerberg antwoord
Antwoord: C
calloc zet elke byte op nul, waardoor elementen van een geheel type de waarde nul krijgen. Index 4 ligt binnen de array van zes elementen en kan dus na de controle van de geheugenallocatie als nul worden gelezen. Nieuwe malloc-opslag zou niet zijn geïnitialiseerd; die regel geldt niet voor deze geslaagde calloc-allocatie.

Vraag 5 van 10

Een functie reserveert geheugen voor een pointerarray en vier afzonderlijke rijblokken. Alle vijf geheugenallocaties slagen en de functie is eigenaar van alle blokken. Hoe moet ze het raster vrijgeven?
  1. Geef de pointerarray vrij en lees daarna de rijpointers.
  2. Geef alleen de pointerarray vrij; die is ook eigenaar van de rijen.
  3. Geef alle vier de rijen vrij en daarna de pointerarray.
  4. Geef elke rij twee keer vrij en daarna de pointerarray.
Toon antwoordVerberg antwoord
Antwoord: C
De rijen zijn vier afzonderlijk toegewezen blokken en hun adressen zijn opgeslagen in de buitenste array. Geef ze vrij zolang die adressen nog beschikbaar zijn en geef daarna het buitenste blok vrij. Alleen de pointerarray vrijgeven laat de rijblokken achter; die eerst vrijgeven maakt ook het later lezen van de items ongeldig.

Vraag 6 van 10

Een wijziging naar een grootte groter dan nul gebruikt int *tmp = realloc(data, bytes); en geeft NULL terug. Het oorspronkelijke blok was geldig. Wat kan de eigenaar nu doen?
  1. Het ongewijzigde oorspronkelijke blok blijven gebruiken of vrijgeven.
  2. De nieuwe bytes via tmp gebruiken nadat die is gewist.
  3. tmp vrijgeven om het oorspronkelijke blok direct vrij te geven.
  4. data pas gebruiken nadat daarvoor opnieuw geheugen is gereserveerd.
Toon antwoordVerberg antwoord
Antwoord: A
Een mislukte realloc met een grootte groter dan nul laat het oorspronkelijke toegewezen blok en de inhoud ongewijzigd. Omdat het resultaat in tmp is opgevangen, bevat data nog steeds het geldige adres. Het vrijgeven van de tijdelijke nulpointer doet niets; daarmee kun je het nog behouden blok waarnaar data verwijst niet vrijgeven.

Vraag 7 van 10

Een dynamische array begint met lengte 0 en capaciteit 3. De capaciteit verdubbelt alleen als de array vol is. Wat zijn de lengte en capaciteit na acht geslaagde toevoegingen?
  1. Lengte 8, capaciteit 8
  2. Lengte 12, capaciteit 12
  3. Lengte 8, capaciteit 9
  4. Lengte 8, capaciteit 12
Toon antwoordVerberg antwoord
Antwoord: D
Bij de vierde toevoeging groeit de capaciteit van 3 naar 6 en bij de zevende van 6 naar 12. Er zijn nu acht waarden in gebruik, dus de lengte is 8. Capaciteit 8 zou passen bij groei tot precies de benodigde grootte, niet bij de gegeven verdubbelingsregel; reservecapaciteit verhoogt het aantal opgeslagen waarden niet.

Vraag 8 van 10

Na een geslaagde geheugenallocatie voert code int *view = owner; free(owner); owner = NULL; uit. Wat gebeurt er als de code daarna view[0] leest?
  1. De code leest geldige opslag omdat view geen nulpointer is.
  2. De code benadert een object waarvan de levensduur is geëindigd.
  3. De code leest nul omdat owner op een nulpointer is gezet.
  4. De code maakt een nieuw toegewezen blok op het vorige adres.
Toon antwoordVerberg antwoord
Antwoord: B
view was een alias naar hetzelfde toegewezen blok, waarvan de levensduur eindigde bij free(owner). De toewijzing van NULL aan owner werkt alleen die pointervariabele bij. Ze verandert view niet en herstelt het oude blok niet, dus de latere leesbewerking is gebruik na vrijgave met ongedefinieerd gedrag.

Vraag 9 van 10

Een programma kopieert de string “network” met strcpy naar een nieuwe heapbuffer. Wat is de kleinste benodigde geheugenallocatie in C-bytes?
  1. 7 bytes
  2. 9 bytes
  3. 16 bytes
  4. 8 bytes
Toon antwoordVerberg antwoord
Antwoord: D
De zeven letters vereisen zeven bytes en strcpy slaat ook het afsluitende nulteken op. De kleinste buffer heeft daarom acht bytes. Zeven bytes bieden ruimte voor de zichtbare letters, maar plaatsen het afsluitende nulteken buiten het toegewezen blok. Dat veroorzaakt een schrijfbewerking buiten de grenzen, ook als afdrukken lijkt te werken.

Vraag 10 van 10

Een globale int *saved is aanvankelijk gelijk aan NULL. Een hulpfunctie voert deze code uit en keert terug. Ints zijn 4 bytes. Memcheck meldt een ongeldige schrijfbewerking direct na het toegewezen blok. Welke doorloopfout en geheugenlekcategorie bij afsluiten volgen hieruit?

int *p = malloc(7 * sizeof *p);
if (p == NULL) return;
for (size_t i = 0; i <= 7; ++i) p[i] = (int)i;
saved = p;
  1. Eén extra int wordt geschreven; het blok is nog bereikbaar.
  2. Vier extra ints worden geschreven; het blok is zeker verloren.
  3. Eén extra int wordt geschreven; het blok is zeker verloren.
  4. Vier extra ints worden geschreven; het blok is nog bereikbaar.
Toon antwoordVerberg antwoord
Antwoord: A
Het blok van zeven ints staat indexen nul tot en met zes toe, maar de lus schrijft ook naar index zeven. De toegangsbreedte is één int van vier bytes. De globale pointer behoudt het beginadres nadat de hulpfunctie terugkeert, dus het niet-vrijgegeven blok is nog bereikbaar, niet zeker verloren.
Set 2: Volg geheugenallocatie- en opruimpaden10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een hulpfunctie geeft een met succes toegewezen array terug via een lokale pointer p. De aanroepende functie slaat het resultaat op in values. Wat blijft direct na terugkeer van de hulpfunctie bestaan?
  1. De variabele p van de hulpfunctie en de toegewezen array.
  2. De variabele p van de hulpfunctie en al haar parameters.
  3. De toegewezen array en de variabele values van de aanroepende functie.
  4. De variabele values van de aanroepende functie, maar geen toegewezen array.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Welke voorwaarde detecteert voor een aantal n dat niet nul is vóór de geheugenallocatie dat n * sizeof *p niet in size_t past?
  1. n > SIZE_MAX / sizeof *p
  2. n * sizeof *p > SIZE_MAX
  3. sizeof p > n / SIZE_MAX
  4. n > SIZE_MAX * sizeof *p
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een functie reserveert geheugen voor buffer a, controleert het resultaat en probeert daarna geheugen voor b te reserveren. De tweede geheugenallocatie mislukt. De functie is eigenaar van a en zal direct met een fout terugkeren. Wat moet worden opgeruimd?
  1. Geef b vrij en keer terug zonder a aan te raken.
  2. Zet a op een nulpointer en keer terug met een fout.
  3. Keer terug met een fout; het opruimen van lokale pointers geeft a vrij.
  4. Geef a vrij voordat je terugkeert met een fout.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een functie declareert static int visits = 0; en voert return ++visits; uit. Welke waarde geeft de derde aanroep terug?
  1. 1
  2. 3
  3. 0
  4. 2
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een stringbuffer heeft een capaciteit van 12 bytes en bevat 11 tekens plus het afsluitende nulteken. Wat moet gebeuren voordat nog een teken wordt toegevoegd?
  1. Overschrijf het afsluitende nulteken en laat de grootte gelijk.
  2. Verhoog de lengte tweemaal en vergroot na de volgende toevoeging.
  3. Schrijf op index 12 en laat het oude afsluitende nulteken staan.
  4. Vergroot de buffer en schrijf daarna het teken en het afsluitende nulteken.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een lus reserveert bij elk van 750 iteraties met succes 48 bytes en verliest de enige pointer naar elk blok zonder het vrij te geven. Hoeveel opslag lekt er?
  1. 36.000 bytes
  2. 750 bytes
  3. 48.000 bytes
  4. 36.048 bytes
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een met succes toegewezen array bevat negen elementen. Wat is de grootste geldige elementindex?
  1. 9
  2. 8
  3. 10
  4. 7
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een programma begint binnen free te crashen na een lus die een heaparray vult. Wat moet het onderzoek controleren voordat de vrijgaveaanroep wordt gewijzigd?
  1. Eerdere schrijfbewerkingen die mogelijk de arraygrenzen overschreden.
  2. Of het programma eindigt als alle opruimcode wordt verwijderd.
  3. Of een afdrukopdracht het gemelde vrijgaveadres verandert.
  4. Of lokale opslag voor dezelfde array de crash verbergt.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Van een niet-vrijgegeven toegewezen blok staat het beginadres bij afsluiten nog in een globale pointer. Welke Valgrind-geheugenlekcategorie beschrijft dit?
  1. Zeker verloren
  2. Indirect verloren
  3. Nog bereikbaar
  4. Mogelijk verloren
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een programma begint met dit gecontroleerde blok en kiest later een tak op basis van p[5]. Een wijziging naar een grootte groter dan nul is geslaagd. ASan meldt niets, terwijl Memcheck een niet-ingestelde waarde bij de tak meldt. Welke oplossing past bij de volledige geschiedenis?

int *p = calloc(4, sizeof *p);
if (p == NULL) return EXIT_FAILURE;
int *tmp = realloc(p, 6 * sizeof *p);
if (tmp == NULL) { free(p); return EXIT_FAILURE; }
p = tmp;
  1. Initialiseer elementen 0 tot en met 3 opnieuw vóór de tak.
  2. Initialiseer element 4 maar laat element 5 ongewijzigd.
  3. Initialiseer elementen 4 en 6 vóór de tak.
  4. Initialiseer elementen 4 en 5 voordat je hun waarden gebruikt.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 3: Groei, levensduren en rapportgegevens10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een hulpfunctie heeft een vaste lokale werkruimte van 20 ints die alleen tijdens de aanroep wordt gebruikt en niet van grootte hoeft te veranderen. Welke opslagkeuze voorkomt een onnodige eigenaar en een onnodig opruimpad?
  1. Een heapblok waarvan het adres verloren gaat.
  2. Een teruggegeven pointer naar een lokale array.
  3. Een heapblok achter een globale variabele zonder eigenaar.
  4. Een gewone lokale array van 20 ints.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Op een systeem met pointers van 8 bytes en ints van 4 bytes gebruikt code int *p = malloc(11 * sizeof p);. Hoeveel bytes vraagt de code aan en wat is er mis met de expressie?
  1. 44 bytes; de code bepaalt de grootte van een element in plaats van de pointer.
  2. 88 bytes; de code bepaalt de grootte van de pointer in plaats van een element.
  3. 11 bytes; sizeof verwijdert de vermenigvuldiging volledig.
  4. 88 bytes; de code reserveert elf blokken met onafhankelijke eigenaren.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Welke C-string bevat een tekenbuffer aanvankelijk na een geslaagde calloc met een grootte groter dan nul?
  1. Een lege string
  2. Een string met spaties
  3. Een string met “0”-tekens
  4. Een onbekende string
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een blok van zes ints wordt beheerd via p. Code bewaart index 4 en een pointer naar dat element. Een poging om via een tijdelijke pointer naar negen ints te groeien mislukt. Een latere verkleining naar drie slaagt en de pointer van de eigenaar wordt bijgewerkt. Kan de selectie daarna worden gebruikt?
  1. Ja; de mislukte groei behield zowel de selectie als de levensduur.
  2. Ja; bereken index 4 opnieuw vanuit het uiteindelijke blok van drie ints.
  3. Nee; de oude pointer is ongeldig en index 4 past niet meer.
  4. Nee; de mislukte groei had het blok van zes ints al vrijgegeven.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een array heeft lengte 6 en capaciteit 8. Twee waarden worden met succes toegevoegd. Code bewaart daarna de pointer van de eigenaar in old, stelt cap = 16 in en wijst data = realloc(data, cap * sizeof *data) toe. Die aanvraag met een grootte groter dan nul mislukt voordat nog een waarde is opgeslagen. Welke toestand herstelt de ongewijzigde array?
  1. Herstel data = old, lengte 9 en capaciteit 16.
  2. Herstel data = old, lengte 8 en capaciteit 8.
  3. Behoud data = NULL, lengte 8 en capaciteit 8.
  4. Herstel data = old, lengte 8 en capaciteit 16.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een functie bewaart haar enige toegewezen blok in buffer. Na het laatste gebruik voert ze buffer = NULL; free(buffer); uit. Wat is het resultaat voor het oorspronkelijke blok?
  1. Het wordt vrijgegeven omdat de variabele ooit het adres bevatte.
  2. Het wordt vrijgegeven en door de toewijzing met nulbytes gevuld.
  3. Het blijft gereserveerd en het adres gaat verloren.
  4. Het blijft gereserveerd maar is nog bereikbaar via buffer.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Na een gecontroleerde geheugenallocatie geeft een functie haar blok vrij via p en roept later free(alias) aan, waarbij alias vóór de vrijgave uit p is gekopieerd. Welke fout is dit?
  1. Een dubbele vrijgave via twee aliassen
  2. Een geheugenlek van twee afzonderlijke bestaande blokken
  3. Een geldige opruiming van twee pointers
  4. Het lezen van een niet-geïnitialiseerde waarde uit nieuwe opslag
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een diagnostische afdrukopdracht naar stdout bevat geen nieuweregelteken, waarna het programma crasht. De tekst verschijnt nooit. Waarom kun je hiermee niet betrouwbaar bepalen hoever de uitvoering is gekomen?
  1. De ontbrekende tekst bewijst dat de afdrukaanroep nooit is uitgevoerd.
  2. De afdrukopdracht heeft de geheugenfout zelf zeker voorkomen.
  3. Het ontbrekende nieuweregelteken heeft de geheugenfout zeker veroorzaakt.
  4. De tekst kan nog in de uitvoerbuffer hebben gestaan.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een ontwikkelaar heeft program-asan gecompileerd met AddressSanitizer en wil een onafhankelijke Memcheck-uitvoering. Welk uitvoerbaar bestand moet Memcheck krijgen?
  1. Dezelfde ASan-versie met uitgeschakelde geheugenlekdetectie.
  2. Een afzonderlijke debugversie zonder AddressSanitizer.
  3. Dezelfde ASan-versie zonder framepointers.
  4. Een rechtstreeks aangeleverd bronbestand in plaats van een uitvoerbaar bestand.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

ASan stopt bij items[n] = 0; direct na een geheugenallocatie voor n ints. De lus gebruikt i <= n, het resultaat is niet gecontroleerd en opruimen ontbreekt. Welke oplossing herstelt de oorzaak en de resterende problemen rond eigenaarschap?
  1. Controleer de geheugenallocatie, gebruik i < n en geef na gebruik vrij.
  2. Reserveer n + 1 ints, behoud de grens en laat opruimen weg.
  3. Controleer de geheugenallocatie, behoud i <= n en geef vóór de lus vrij.
  4. Behoud de grootte en grens, zet het blok op nul en geef na gebruik vrij.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 4: Herstel eigenaarschap onder veranderende omstandigheden10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Welke opslagduur heeft een gewone globale array op bestandsniveau?
  1. Statische opslagduur
  2. Automatische opslagduur
  3. Toegewezen opslagduur
  4. Een nieuwe opslagduur per aanroep
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een bibliotheek heeft met calloc geheugen voor vier gehele tellers gereserveerd en het blok later met succes vergroot naar zeven tellers. Wat moet gebeuren voordat teller 6 voor het eerst wordt verhoogd?
  1. Verhoog de teller direct, omdat bij elke groei de nullen behouden blijven.
  2. Geef alle zeven tellers vrij en maak ze opnieuw om oude waarden te behouden.
  3. Wijs de bedoelde beginwaarde toe voordat je de teller verhoogt.
  4. Zet het adres op NULL voordat je het grotere blok gebruikt.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

De buitenste array van een raster bevat pointers naar rijen die afzonderlijk eigendom zijn. De maker heeft rij 0 en 1 gemaakt. Een leesfunctie leent rij 0 totdat die terugkeert. Het maken van rij 2 mislukt terwijl de leesfunctie actief is. Latere items zijn nooit ingesteld. Welke opruiming bij fouten behoudt elke levensduur?
  1. Geef rij 0 en 1 vrij, laat de leesfunctie eindigen en geef de buitenste array vrij.
  2. Laat de leesfunctie eindigen, geef de buitenste array vrij en lees en geef de rijen vrij.
  3. Laat de leesfunctie eindigen, geef elke geplande rij vrij en daarna de buitenste array.
  4. Laat de leesfunctie eindigen, geef rij 0 en 1 vrij en daarna de buitenste array.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een toegewezen blok van vier ints wordt beheerd via p. Een poging om via tmp de grootte te wijzigen naar zeven ints mislukt, waardoor p ongewijzigd blijft. Daarna voert code p = realloc(p, 9 * sizeof *p) uit en ook die aanvraag mislukt. Er zijn geen aliassen. Welk toegewezen blok is nu verloren?
  1. Het eerst aangevraagde blok van zeven ints blijft gereserveerd maar is verloren.
  2. Het oorspronkelijke blok van vier ints blijft gereserveerd maar is verloren.
  3. Het daarna aangevraagde blok van negen ints blijft gereserveerd maar is verloren.
  4. Er blijft geen blok gereserveerd omdat beide aanvragen voor groottewijziging mislukten.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

De capaciteit van een dynamische array verdubbelt vanaf 5 zodra die vol raakt. De array begint leeg met die capaciteit en ontvangt 18 waarden. Als elke vergroting het blok verplaatst, welke capaciteit en welk maximaal totaal aantal elementkopieën levert de groei op?
  1. Capaciteit 20; hoogstens 15 gekopieerde elementen.
  2. Capaciteit 20; hoogstens 18 gekopieerde elementen.
  3. Capaciteit 18; hoogstens 23 gekopieerde elementen.
  4. Capaciteit 40; hoogstens 35 gekopieerde elementen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een functie reserveert geheugen voor een resultaat, vult het, geeft het vrij en geeft daarna de pointer terug aan de aanroepende functie. Die moet het resultaat lezen. Welke aanpassing van het eigenaarschap past?
  1. Geef het vrijgegeven resultaat terug en laat de aanroepende functie op een nulpointer controleren.
  2. Wis de pointer vóór terugkeer en behoud dezelfde interface.
  3. Geef het resultaat twee keer vrij zodat geen ongeldig blok overblijft.
  4. Geef het bestaande resultaat terug en laat de aanroepende functie het later vrijgeven.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Code reserveert char **labels = malloc(4 * sizeof *labels);, controleert het resultaat en voert daarna direct strcpy(labels[0], "east"); uit. Welk ontwerp herstelt het eerste item en maakt het eigenaarschap duidelijk?
  1. Reserveer vijf pointerplaatsen en kopieer daarna via item 0 zoals geschreven.
  2. Zet item 0 op de literal “east” en overschrijf die literal met strcpy.
  3. Reserveer vijf chars voor item 0, controleer het, kopieer en geef het later vrij.
  4. Zet item 0 op NULL en kopieer daarna vijf chars via dat item.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een geslaagde wijziging van de grootte verkleint een blok van negen geïnitialiseerde ints naar vier. Welke inhoud blijft gegarandeerd behouden?
  1. De oorspronkelijke waarden op indexen 5 tot en met 8.
  2. De oorspronkelijke waarden op indexen 0 tot en met 3.
  3. Alle negen waarden, zolang geen nieuwe geheugenallocatie plaatsvindt.
  4. Vier nieuwe nulwaarden, die het oorspronkelijke begindeel vervangen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een test overschrijdt een lokale array met één element, maar blijft binnen verder geldig stapelgeheugen. Een Memcheck-uitvoering mist dit. Welke volgende controle past het best bij deze gegevens?
  1. Herhaal Memcheck met de geheugenlekcategorieën zichtbaar.
  2. Bouw opnieuw met ASan en voer dezelfde invoer opnieuw uit.
  3. Beschouw de toegang als geldig na een uitvoering zonder fouten.
  4. Vervang de array door een groter ongecontroleerd heapblok.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een controlehulpmiddel meldt dubbele vrijgave. Door beide vrijgaveaanroepen te verwijderen crasht de uitvoering niet meer, maar blijft een toegewezen blok achter. Welke oplossing moet die wijziging vervangen?
  1. Laat beide vrijgaven weg en vertrouw bij afsluiten op het besturingssysteem.
  2. Behoud beide vrijgaven maar zet een geleende alias op een nulpointer.
  3. Verplaats beide vrijgaven naar vóór het laatste gebruik om de levensduur te verkorten.
  4. Behoud één vrijgave na het laatste gebruik onder verantwoordelijkheid van één eigenaar.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 5: Volg volledige geheugengeschiedenissen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een functie geeft het adres van een lokale tekenarray terug. De aanroepende functie drukt soms de verwachte tekst af en crasht soms. Wat verklaart beide uitkomsten?
  1. De lokale array werd statisch omdat het adres buiten de functie terechtkwam.
  2. De terugkeer droeg eigenaarschap van een geldig heapblok over.
  3. De verwachte tekst bewijst dat de array tijdens die uitvoering bestond.
  4. De array eindigde bij terugkeer; de toegang door de aanroepende functie is ongeldig.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Wat meet sizeof samples voor een gewone double *samples?
  1. De grootte van één double waarnaar wordt gewezen.
  2. Het aantal bytes van het hele toegewezen blok.
  3. De grootte van de pointervariabele.
  4. Het aantal geïnitialiseerde array-elementen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een functie geeft een nieuw toegewezen string terug. De aanroepende functie geeft die door aan een afdrukfunctie die de string alleen leent; de afdrukfunctie geeft die per ongeluk vrij. De aanroepende functie leest en geeft de string daarna vrij. Welke wijziging van het eigenaarschap herstelt het volledige pad?
  1. Verwijder de vrijgave van de aanroepende functie; behoud haar leesbewerking na de afdrukfunctie.
  2. Verwijder de vrijgave van de afdrukfunctie; behoud de laatste vrijgave van de aanroepende functie.
  3. Behoud beide vrijgaven; zet alleen de lokale pointer van de afdrukfunctie op een nulpointer.
  4. Verplaats de vrijgave van de aanroepende functie naar vóór de afdrukfunctie om het eigenaarschap eerder te beëindigen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Memcheck meldt dat een voorwaardelijke sprong afhangt van een niet-geïnitialiseerde waarde. Welke optie helpt te vinden waar die waarde vandaan kwam?
  1. --track-origins=yes
  2. --leak-check=no
  3. --show-leak-kinds=all
  4. --error-limit=no
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een tekstbuffer begint met capaciteit 6, lengte 4 en een afsluitend nulteken op index 4. De buffer voegt X met succes toe zonder te groeien en werkt het afsluitende nulteken bij. Het volgende teken Y veroorzaakt een aanvraag voor groei via een tijdelijke pointer, maar die mislukt. De API belooft de string bij een fout te behouden. Welke toestand blijft over?
  1. Lengte 4, capaciteit 6; zowel X als Y wordt verwijderd.
  2. Lengte 5, capaciteit 6; X blijft behouden en het afsluitende nulteken staat op 5.
  3. Lengte 6, capaciteit 6; Y vervangt het afsluitende nulteken op 5.
  4. Lengte 5, capaciteit 12; X blijft behouden en het afsluitende nulteken staat op 5.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een dienst verwerkt drie taken. Elke taak reserveert met succes 24 bytes in de lokale a en 40 bytes in de lokale b. Op het foutpad geeft de taak a vrij, stelt b = NULL in en keert terug. Er blijven geen aliassen over. Welke totale grootte aan toegewezen geheugen gaat verloren en welke wijziging voorkomt dit?
  1. 192 bytes; geef beide pointervariabelen na hun terugkeer vrij.
  2. 72 bytes; stop met het vrijgeven van a op het foutpad.
  3. 40 bytes; geef alleen b van de laatste taak na de lus vrij.
  4. 120 bytes; geef elk blok van b vrij voordat je de pointer wist.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Er is een geleende pointer naar element 2 van een array. Code vergroot de array met succes via een tijdelijke pointer en werkt de pointer van de eigenaar bij, waarna ze via de oude geleende pointer schrijft. Het adres is niet veranderd. Welke regel maakt deze toegang toch ongeldig?
  1. Elke wijziging van de grootte verwijdert de inhoud van het oorspronkelijke begindeel.
  2. De pointer van de eigenaar en de geleende pointer moeten een verschillend adres hebben.
  3. De geslaagde wijziging van de grootte beëindigde de levensduur van het oude object.
  4. Bij vergroten blijven alleen waarden behouden die via index nul worden bereikt.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Ints zijn 4 bytes en pointers zijn 8 bytes. Code gebruikt int *p = malloc(6 * sizeof p);, controleert of dit slaagt, initialiseert indexen 0 tot en met 5 en telt daarna indexen 0 tot en met 11 op. Welke diagnose past bij de hele som?
  1. De laatste zes leesbewerkingen blijven binnen de grenzen maar lezen niet-geïnitialiseerde waarden.
  2. De laatste zes leesbewerkingen liggen buiten een toegewezen blok van 24 bytes.
  3. Alle twaalf leesbewerkingen zijn geldig omdat geheugenallocatie ze initialiseert.
  4. Alleen de laatste leesbewerking overschrijdt het einde van het te grote blok.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een programma stopt met een segmentatiefout op een regel die p[index] leest. Wat moet je aan die toegang controleren?
  1. De herkomst van de pointer, de levensduur van het object en het indexbereik.
  2. Alleen de index, omdat elke pointer die geen nulpointer is naar een bestaand object wijst.
  3. Alleen de vrijgaveregel, omdat alle fouten dubbele vrijgaven zijn.
  4. Alleen uitvoerbuffering, omdat een leesbewerking van een array geen fout kan veroorzaken.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een rapport van een hulpmiddel toont een leesbewerking in display, een eerdere vrijgave in reset en een geheugenallocatie in load. De aanroepende functie roept load, reset en daarna display aan op hetzelfde resultaat. Ze heeft die weergave nodig. Welke oplossing volgt uit de geschiedenis van het blok?
  1. Vergroot de grootte in load en laat de aanroepvolgorde ongewijzigd.
  2. Wis alleen de lokale pointer van reset en behoud de latere leesbewerking.
  3. Houd het resultaat in leven tijdens display en geef het daarna vrij.
  4. Negeer reset omdat de gemelde fout in display optrad.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Kom je niet uit je eigen opgave? Huiswerkhulp

Kernbegrippen

63 Kernbegrippen, elk met een definitie

A

Aanroepoverzicht
Een aanroepoverzicht vermeldt de functieaanroepen die bij een crash of fout actief zijn en helpt de relevante toegang en het pad daarheen te vinden.
Begin bij het eerste relevante frame in je eigen code en onderzoek daarna hoe de pointer zijn waarde kreeg.
Aanroepstapel
De aanroepstapel is het gebied dat gewoonlijk wordt gebruikt om actieve functieaanroepen, hun lokale objecten, parameters en terugkeerinformatie bij te houden.
Een niet-geoptimaliseerde recursieve functie kan frames blijven toevoegen totdat de beschikbare stapelruimte is uitgeput.
AddressSanitizer
AddressSanitizer is door de compiler toegevoegde instrumentatie om ongeldige geheugentoegang en vrijgaven te detecteren terwijl een geïnstrumenteerd uitvoerbaar bestand draait.
Een bouw met ASan kan een overschrijding van een lokale array aanwijzen die binnen verder geldig stapelgeheugen blijft.
Afsluitend nulteken
Het afsluitende nulteken is het teken met waarde nul dat het einde van een C-string markeert en één byte na de zichtbare tekens inneemt.
De string “west” met vier tekens heeft vijf bytes nodig als die samen met het afsluitende nulteken wordt gekopieerd.
Alias
Een alias is een andere pointer naar hetzelfde object, die de levensduur van dat object deelt zonder te worden bijgewerkt wanneer een andere pointervariabele verandert.
De pointer van de eigenaar na vrijgave op NULL zetten laat opgeslagen aliassen onbruikbaar voor latere leesbewerkingen.
Allocatieplaats
Een allocatieplaats is de plek in het programma die een blok aanvraagt en die in hulpmiddelrapporten wordt getoond om vast te stellen waar die opslag begon.
De allocatieplaats in een geheugenlekrapport kan binnen een hulpfunctie liggen, terwijl de juiste vrijgave bij de aanroepende functie hoort.
Array met variabele lengte
Een array met variabele lengte is een array waarvan de grootte tijdens de uitvoering wordt ingesteld en die een automatische levensduur heeft, meestal op de stapel.
De declaratie int a[n] biedt niet de controle op een nulresultaat bij fouten die geheugenallocatie op de heap wel biedt.
Automatische opslag
Automatische opslag hoort bij gewone lokale objecten en parameters waarvan de levensduur eindigt wanneer de uitvoering hun blok verlaat.
Een lokale array van een hulpfunctie houdt op te bestaan wanneer de hulpfunctie terugkeert, ook als de aanroepende functie het adres van de array heeft bewaard.

B

Buffer die de aanroepende functie beschikbaar stelt
Een buffer die de aanroepende functie beschikbaar stelt is opslag die aan een functie wordt geleverd voor haar uitvoer, zodat de aanroepende functie de grootte en levensduur blijft bepalen.
Een aanroepende functie kan haar eigen array van 16 bytes doorgeven aan een hulpfunctie die die vult met een korte begroeting.
Bufferhergebruik
Bufferhergebruik houdt één geschikt toegewezen blok aan voor herhaalde bewerkingen, waarbij de inhoud wordt vervangen voordat het blok uiteindelijk wordt vrijgegeven.
Een lus kan één werkruimte van 100 bytes hergebruiken in plaats van bij elke doorgang een blok toe te wijzen en achter te laten.

C

calloc
De functie calloc reserveert een array met een aantal en elementgrootte, zet de bytes op nul en geeft een nulpointer terug als de geheugenallocatie mislukt.
Gehele tellers uit een gecontroleerde calloc-aanvraag beginnen op nul vóór de eerste verhoging.
Capaciteit
De capaciteit van een array is het aantal elementplaatsen dat in het blok is gereserveerd, inclusief ongebruikte plaatsen die nog niet tot de logische inhoud behoren.
Een mislukte aanvraag voor groei moet de capaciteit ongewijzigd laten omdat er geen extra plaatsen zijn verkregen.
Controle-uitvoering
Een controle-uitvoering voert het herstelde programma uit met een controlehulpmiddel om het geteste pad te bevestigen. Daarna volgen uitvoeringen met invoer die andere paden doorloopt.
Test na een uitvoering zonder fouten met de invoer die de fout reproduceerde ook lege invoer, groeigrenzen en vroege terugkeer op ontbrekende opruiming.

D

Dubbele vrijgave
Dubbele vrijgave is een poging om een blok vrij te geven waarvan de levensduur al door een eerdere vrijgave is geëindigd, ook via een andere alias.
Een eigenaar en een gebruiker van een geleende pointer kunnen niet ieder free aanroepen op wat één gedeeld bestaand toegewezen blok was.
Dynamische array
Een dynamische array is toegewezen opslag voor elementen waarvan de lengte en capaciteit worden bijgehouden, zodat de array kan groeien wanneer meer waarden binnenkomen.
Een dynamische array kan tien waarden in zestien plaatsen bevatten, zodat reservecapaciteit overblijft voor latere toevoegingen.

E

Eén-ernaastfout
Een één-ernaastfout maakt een grens één positie te groot of te klein, vaak doordat het aantal elementen als ongeldige laatste index wordt meegenomen.
Door i <= n te veranderen in i < n bezoekt een doorloop vanaf nul precies de n bedoelde elementen.
Eerste gemelde fout
De eerste gemelde fout is de vroegste bevinding van een controlehulpmiddel tijdens een uitvoering, vaak het beste beginpunt omdat latere fouten uit de schade kunnen voortkomen.
Herstel de eerste grensoverschrijding voordat je elke latere allocatorfout in hetzelfde rapport onderzoekt.
Eigenaarschap
Eigenaarschap is de verantwoordelijkheid om een toegewezen blok na het laatste gebruik vrij te geven of die plicht bewust over te dragen aan een ander deel van het programma.
Een functie die een kopie maakt, draagt het eigenaarschap over aan de aanroepende functie wanneer ze een nieuw toegewezen resultaat teruggeeft.

F

free
De functie free geeft een nog bestaand toegewezen blok vrij voor hergebruik wanneer het beginadres wordt doorgegeven en doet niets wanneer NULL wordt doorgegeven.
De aanroepende functie geeft een teruggegeven stringkopie pas vrij nadat alle gebruikers van geleende pointers klaar zijn met lezen.

G

Gebruik na vrijgave
Gebruik na vrijgave is toegang tot een toegewezen object na vrijgave. Dit kan gegevens opleveren die oud lijken, geheugen beschadigen of een crash veroorzaken.
Een vrijgegeven string via een opgeslagen alias afdrukken is ongeldig, ook als de verwachte woorden verschijnen.
Gedeelde statische buffer
Een gedeelde statische buffer is blijvende opslag voor een functieresultaat die door aanroepen opnieuw wordt gebruikt, zodat een latere aanroep een eerder resultaat kan overschrijven.
Twee pointers die door opeenvolgende aanroepen zijn teruggegeven, kunnen naar dezelfde statische array verwijzen in plaats van naar onafhankelijke teksten.
Geheugenallocatie
Een geheugenallocatie is een aanvraag voor een gereserveerd geheugenblok; een geslaagde aanvraag levert het beginadres voor later gebruik en vrijgave.
Een programma controleert of de geheugenallocatie is geslaagd voordat het het eerste element van de aangevraagde array opslaat.
Geheugenlek
Een geheugenlek is een toegewezen blok dat niet meer nodig is maar niet wordt vrijgegeven, vaak doordat het programma de laatste pointer ernaartoe is kwijtgeraakt.
De enige pointer van de eigenaar overschrijven met een nieuw toegewezen blok kan het oude blok voorgoed achterlaten.
Geleende pointer
Een geleende pointer geeft toegang tot een bestaand object terwijl een ander deel van het programma verantwoordelijk blijft voor de beslissing wanneer het wordt vrijgegeven.
Een afdrukfunctie kan tijdens haar aanroep een string lenen zonder toestemming te krijgen om het blok van de aanroepende functie vrij te geven.
Geneste geheugenallocatie
Een geneste geheugenallocatie is een ontwerp met afzonderlijk toegewezen binnenste blokken waarvan de adressen in een toegewezen buitenste container worden bewaard.
Bij een raster met afzonderlijke rijen moeten de rijen worden opgeruimd voordat de buitenste pointerarray wordt vrijgegeven.

H

Heap
De heap is de geheugenvoorraad waaruit de allocator blokken levert waarvan de levensduur door geheugenallocatie en vrijgave wordt bepaald.
Als een functie het adres van een toegewezen array teruggeeft, blijft het blok op de heap bestaan zodat de aanroepende functie het kan gebruiken en vrijgeven.
Heapoverzicht
Een heapoverzicht rapporteert de geheugenallocatieactiviteiten en resterende heapopslag bij afsluiten en ondersteunt het onderzoek naar blokken die niet zijn vrijgegeven.
Lees de totalen van resterende blokken samen met de geheugenlekcategorieën in plaats van elk blok als verloren te beschouwen.
Herkomsttracering
Herkomsttracering volgt een niet-geïnitialiseerde waarde terug naar de opslag die die waarde leverde en helpt zo de ontbrekende waardetoewijzing te vinden.
Met --track-origins=yes kan Memcheck verklaren waarom een latere tak afhangt van niet-ingestelde heapgegevens.

I

Indirect verloren
Een toegewezen blok is volgens Memcheck indirect verloren als het alleen bereikbaar is via een ander blok dat zelf verloren is gegaan.
Als een buitenste array met adressen van rijen waarvan het programma eigenaar is verloren gaat, kunnen ook de toegewezen rijblokken indirect verloren raken.
Initialisatie
Initialisatie legt een waarde vast voordat die wordt gelezen, via een eerste toewijzing of een geschikte geheugenallocatie met nullen.
Een teller moet een bepaalde beginwaarde hebben omdat verhogen eerst de vorige waarde leest.
Instrumentatie
Instrumentatie voegt tijdens een bouw controlecode toe, zodat het resulterende programma bepaalde fouten kan detecteren terwijl de geteste paden worden uitgevoerd.
Door -fsanitize=address toe te voegen tijdens het compileren en koppelen, maak je een uitvoerbaar bestand met ASan-instrumentatie.
Invoer die de fout reproduceert
Invoer die de fout reproduceert is een testgeval dat het waargenomen probleem betrouwbaar bereikt en zo een concreet beginpunt biedt voor debuggen en controle.
Maak invoer die een fout veroorzaakt kleiner terwijl de fout behouden blijft, zodat het eerste relevante rapport gemakkelijker te interpreteren is.

L

LeakSanitizer
LeakSanitizer controleert bij afsluiten op gelekte toegewezen blokken en kan samen met ASan worden gebruikt; beschikbaarheid en standaardinstellingen hangen af van het platform.
Linux-versies met ASan schakelen geheugenlekcontroles normaal in; sommige macOS-toolchains kunnen die niet bieden.
Lengte
De lengte van een array is het aantal geïnitialiseerde elementen dat momenteel in gebruik is en bepaalt het bereik voor berekeningen met de opgeslagen waarden.
Na het toevoegen van een vijfde waarde aan beschikbare opslag wordt de lengte vijf, ook als de capaciteit acht blijft.
Levensduur van een object
De levensduur van een object is de periode waarin het object in gereserveerde opslag bestaat en kan worden benaderd via geldige pointers die ernaar wijzen.
Een geslaagde realloc beëindigt de levensduur van het oude object, ook als het teruggegeven adres onveranderd lijkt.
Lezen van een niet-geïnitialiseerde waarde
Bij het lezen van een niet-geïnitialiseerde waarde wordt een waarde gebruikt die nooit is ingesteld, ook als het gaat om een element binnen de grenzen van nieuwe of pas vergrote opslag.
Drie toegewezen ints optellen nadat aan slechts twee een waarde is toegekend, leest nog steeds een niet-ingestelde derde waarde.

M

malloc
De functie malloc reserveert een aangevraagd aantal bytes en geeft een blokadres of een nulpointer terug, zonder de opgeslagen waarden te initialiseren.
Een array van gehele getallen die met malloc is verkregen, heeft toewijzingen nodig voordat de elementen worden opgeteld of vergeleken.
Meetkundige groei
Meetkundige groei vergroot de capaciteit met een vermenigvuldigingsfactor, zoals bij verdubbelen, om herhaald kopiëren over veel toevoegingen te beperken.
Een volle array met capaciteit vier groeit naar acht en daarna naar zestien wanneer de volgende groeigrens wordt bereikt.
Memcheck
Memcheck is het standaardhulpmiddel van Valgrind voor geheugencontrole. Het volgt tijdens een uitvoering ongeldige toegang, levensduren van toegewezen blokken, niet-ingestelde waarden en niet-vrijgegeven blokken.
Voer een gewoon uitvoerbaar debugbestand uit onder Memcheck om naast de geheugengeschiedenissen ook bronregels te krijgen.
MemorySanitizer
MemorySanitizer is Clangs controlehulpmiddel voor het gebruik van niet-geïnitialiseerde waarden op ondersteunde systemen. Het behandelt een soort fout die ASan normaal niet volgt.
Gebruik het in een afzonderlijke ondersteunde bouw in plaats van de instrumentatie met AddressSanitizer te combineren.
Met nullen gevuld geheugen
In met nullen gevuld geheugen is elke bit op nul gezet. Dat levert nul op voor gehele getallen, maar garandeert niet op overdraagbare wijze drijvendekommanul of nulpointers.
Een tekenbuffer met een grootte groter dan nul uit calloc begint met een nulteken en bevat een lege string.
Mogelijk verloren
Een toegewezen blok is in Memcheck mogelijk verloren als alleen een pointer naar een positie binnen het blok is gevonden, waardoor onzeker is waar het beginadres van het blok is.
Onderzoek of een verschuivende pointer de pointer van de eigenaar heeft vervangen in plaats van een afzonderlijke beginpointer te bewaren.

N

Nog bereikbaar
Een toegewezen blok is in Valgrind nog bereikbaar als bij afsluiten een pointer overblijft, hoewel het programma het blok niet heeft vrijgegeven.
Een globale pointer die het beginadres van een niet-vrijgegeven blok bewaart, maakt het bereikbaar voor de controle bij afsluiten.
Nulpointer
Een nulpointer is een pointerwaarde die geen object aanduidt en die geheugenallocatiefuncties teruggeven wanneer ze een aangevraagd blok van een grootte groter dan nul niet kunnen leveren.
Een controle op een nulpointer stuurt de uitvoering naar een foutpad voordat code een mislukt toegewezen blok probeert te benaderen.

O

Ongedefinieerd gedrag
Ongedefinieerd gedrag ontstaat wanneer C na een regelovertreding geen eisen stelt aan het resultaat van het programma, waardoor crashes of stille beschadiging mogelijk zijn.
Een schrijfbewerking buiten de grenzen kan in één test onschadelijk lijken en bij andere invoer een andere waarde beschadigen.
Ongeldige pointer naar een niet meer bestaand object
Een ongeldige pointer naar een niet meer bestaand object verwijst naar een object waarvan de levensduur is geëindigd, zodat er geen geldig oud object meer is voor latere toegang via die pointer.
Een pointer naar de voormalige lokale array van een hulpfunctie is direct na terugkeer van die functie ongeldig.
Opruimpad
Een opruimpad is code die verkregen hulpbronnen vrijgeeft voordat een functie eindigt, ook wanneer het werk na gedeeltelijk succes stopt.
Als een tweede geheugenallocatie mislukt, geeft het foutpad het eerste blok vrij voordat het een fout teruggeeft.
Opslagduur
De opslagduur van een object bepaalt hoelang de opslag ervan is gereserveerd, onder meer of die eindigt met een blok, bij vrijgave of bij het afsluiten van het programma.
Een lokale pointer en de toegewezen array waarnaar die wijst, kunnen een verschillende opslagduur hebben, ook al bevat de pointer het adres van de array.
Overloop bij de berekening van de geheugengrootte
Overloop bij de berekening van de geheugengrootte ontstaat wanneer een berekening van aantal naar bytes het bereik van size_t overschrijdt en modulair terugloopt naar een kleinere weergeefbare waarde.
Controleer het aantal elementen aan de hand van SIZE_MAX / sizeof *p voordat je het met de elementgrootte vermenigvuldigt.
Overschrijding van heapbuffergrenzen
Overschrijding van heapbuffergrenzen is lezen of schrijven vóór of voorbij de grenzen van een toegewezen blok, ook als het omringende adres toevallig toegankelijk is.
Een lus die index tien bereikt in een toegewezen blok van tien elementen overschrijdt het geldige elementbereik van het blok.

P

Pointer naar een positie binnen een object
Een pointer naar een positie binnen een object verwijst naar een positie binnen een object, zoals een array-element, in plaats van naar het beginadres van het toegewezen blok.
Bewaar een elementindex tijdens een geslaagde groottewijziging en bereken de pointer naar een positie binnen het object opnieuw vanuit het resultaat.

R

realloc
De functie realloc vervangt een toegewezen blok door een object met de aangevraagde grootte, behoudt het passende begindeel en kan het adres veranderen.
Een blok van vier ints vergroten naar zes behoudt de eerste vier waarden, maar laat twee nieuwe elementen niet ingesteld.

S

Segmentatiefout
Een segmentatiefout is een fout van het besturingssysteem door verboden geheugentoegang, zoals de veelvoorkomende dereferentie van een nulpointer of een schrijfbewerking naar alleen-lezengeheugen.
Een ongeldige schrijfbewerking kan eerst gegevens beschadigen en later in een andere functie een segmentatiefout veroorzaken.
size_t
Het type size_t is het geheelgetaltype zonder teken voor objectgroottes en byteaantallen bij geheugenallocatie, inclusief het resultaat van sizeof.
Een dynamische array gebruikt size_t-variabelen voor de lengte en capaciteit zodat die objectgroottes kunnen beschrijven.
sizeof
De operator sizeof geeft de grootte van een type of object in C-bytes; op een pointer meet die de pointer in plaats van het toegewezen blok waarnaar die wijst.
De aanvraag n * sizeof *p volgt het elementtype, terwijl sizeof p de lengte van de array niet kan achterhalen.
Stapeloverloop
Bij stapeloverloop raakt de beschikbare ruimte voor actieve aanroepen en automatische objecten uitgeput, vaak door diepe recursie of een enorme lokale array.
Een niet-geoptimaliseerde recursieve functie zonder stopvoorwaarde kan frames blijven toevoegen totdat die faalt.
Statische opslag
Statische opslag is opslag die tijdens de hele uitvoering van het programma bestaat en wordt gebruikt voor globale objecten en lokale variabelen gedeclareerd met static.
Een statische teller binnen een functie behoudt zijn bijgewerkte waarde wanneer de volgende aanroep begint.
Stringliteral
Een stringliteral is tekst tussen aanhalingstekens in C-broncode, met statische opslag die niet via een pointer mag worden gewijzigd.
Gebruik char word[] = "hello"; wanneer een lokale beschrijfbare kopie nodig is in plaats van een pointer naar de literal.

T

Tijdelijke pointer
Een tijdelijke pointer ontvangt het resultaat van een groottewijziging voordat de pointer van de eigenaar wordt bijgewerkt, zodat het adres van het oude blok behouden blijft als de aanvraag mislukt.
Bij tmp = realloc(p, bytes) blijft p bij een nulresultaat beschikbaar voor verder gebruik of opruiming.

U

Uitvoerbuffering
Uitvoerbuffering bewaart afgedrukte tekst tijdelijk in het geheugen voordat die wordt weggeschreven, zodat een crash kan verhinderen dat recente voortgangsmeldingen verschijnen.
Door fflush(stdout) na een voortgangsmelding aan te roepen, maak je de gebufferde tekst beschikbaar vóór de volgende bewerking.
UndefinedBehaviorSanitizer
UndefinedBehaviorSanitizer voegt controles toe voor bepaalde soorten ongedefinieerd gedrag buiten adresfouten, zoals overloop bij gehele getallen met teken, en kan samen met ASan worden gebruikt.
Een ontwikkelversie kan -fsanitize=address,undefined gebruiken om de twee verenigbare verzamelingen controles in te schakelen.

V

Verloren geheugenallocatie
Een verloren geheugenallocatie is een nog gereserveerd blok waarvan geen bruikbaar adres meer wordt bewaard, zodat het programma het niet meer op de normale manier kan vrijgeven.
De enige pointer van de eigenaar op NULL zetten zonder eerst free aan te roepen, laat het adres van het toegewezen blok verloren gaan.

Z

Zeker verloren
Een toegewezen blok is in Memcheck zeker verloren als er geen pointer meer bestaat, zodat de gereserveerde opslag niet via een opgeslagen adres kan worden vrijgegeven.
Een hulpfunctie die haar enige pointer met eigenaarschap achterlaat, kan een zeker verloren blok in het rapport bij afsluiten veroorzaken.

Over deze les

Door StudeliFout melden

Maak je eigen Studiemateriaal

Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.

Aan de slag