Debuggen, ongedefinieerd gedrag en veilig C programmeren

Leer programmeerfouten opsporen en voorkomen waardoor C-programma’s vastlopen, verkeerde antwoorden geven of onveilig worden. Schakel compilerwaarschuwingen in en lees wat ze betekenen, spoor de oorzaak van crashes en verkeerde waarden stap voor stap op met gdb of lldb en herken ongedefinieerd gedrag bij berekeningen, pointers en geheugengebruik. Controleer je aannames met asserties, spoor geheugenfouten en ongedefinieerd gedrag op met AddressSanitizer en UndefinedBehaviorSanitizer en schrijf veiligere code door regels voor veilig programmeren te volgen voor strings, invoer en fouten. Weeg ten slotte af wat het gebrek aan geheugenveiligheid van C kost en hoeveel controle C je geeft.

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

Wat je leert

  1. Schakel de waarschuwingen van de compiler in, lees de diagnostische meldingen en kies bouwinstellingen die programmeerfouten vroeg zichtbaar maken, nog voordat het programma wordt uitgevoerd.
    • Schakel waarschuwingen in en lees compilermeldingen
      Compileer met een strenge set waarschuwingsopties, lees elke diagnostische melding regel voor regel en verhelp de oorzaak in plaats van de melding te onderdrukken.
    • Kies bouwinstellingen voor debuggen, testen en productie
      Gebruik afzonderlijke debugversies en productieversies, weet wat -g en de optimalisatieniveaus veranderen en voeg waar nodig statische analyse en opties voor beveiligingsversterking toe.
  2. Spoor programmeerfouten systematisch op: reproduceer en isoleer het falen en gebruik vervolgens gdb of lldb om het programma te pauzeren, het stapsgewijs te doorlopen, waarden te bekijken en de werkelijke oorzaak van een crash of verkeerde waarde te vinden.
    • Debug systematisch: reproduceer het probleem, isoleer de oorzaak en toets een hypothese
      Maak een vage fout precies, lees wat de crashmelding je vertelt en baken de oorzaak af met kleine, gecontroleerde experimenten.
    • Voer een programma uit, pauzeer het en doorloop het stapsgewijs met gdb of lldb
      Start een programma onder gdb of lldb, stel breekpunten in, doorloop het regel voor regel en bekijk variabelen en arrays terwijl het programma is gepauzeerd.
    • Achterhaal de oorzaak van crashes en onverwachte waarden
      Gebruik de aanroepstapel na een crash, verplaats je tussen stackframes en gebruik gegevensbreekpunten, voorwaardelijke breekpunten en geheugendumps om te vinden waar een verkeerde waarde vandaan komt.
  3. Leg uit wat ongedefinieerd gedrag is en waarom het C-programma’s onvoorspelbaar maakt, en herken de meest voorkomende oorzaken ervan bij berekeningen, expressies, pointers en geheugengebruik.
    • Begrijp wat ongedefinieerd gedrag betekent
      Onderscheid ongedefinieerd, door de implementatie bepaald en niet-gespecificeerd gedrag en leg uit waarom code met ongedefinieerd gedrag lijkt te kunnen werken en daarna kan falen.
    • Herken ongedefinieerd gedrag in berekeningen en expressies
      Herken overloop bij gehele getallen met teken, ongeldige delingen en bitverschuivingen, conversies buiten het waardenbereik, niet-geordende neveneffecten en het lezen van niet-geïnitialiseerde waarden, en schrijf de controles die deze problemen voorkomen.
    • Herken ongedefinieerd gedrag bij pointers, arrays en geheugengebruik
      Herken toegang buiten de grenzen, ongeldige strings, nulpointers en pointers naar niet meer bestaande objecten, gebruik na vrijgave en verkeerde allocatiegroottes, en ken voor elk het veilige patroon.
  4. Laat een programma tijdens de uitvoering zijn eigen aannames controleren: leg die vast met asserties en bouw testversies met sanitizers die stoppen bij geheugenfouten en ongedefinieerd gedrag en precies melden waar die optraden.
    • Leg aannames vast met asserties
      Gebruik assert voor voorwaarden die alleen door een programmeerfout onwaar kunnen zijn, begrijp wat NDEBUG doet en houd asserties gescheiden van de afhandeling van verwachte fouten.
    • Spoor geheugenfouten op met AddressSanitizer
      Bouw een testversie met AddressSanitizer, lees de foutrapporten en weet wat het hulpmiddel vindt, wat het mist en wat het kost.
    • Detecteer ongedefinieerd gedrag met UBSan en ontwikkel een vaste controleroutine
      Gebruik UndefinedBehaviorSanitizer en leer de andere sanitizers kennen. Combineer vervolgens waarschuwingen, sanitizers en tests tot een routine die fouten vroeg opspoort.
  5. Pas regels voor veilig programmeren zoals de SEI CERT C-regels toe op strings, buffers, invoer en fouten, en weeg af wat het gebrek aan geheugenveiligheid van C kost en wat C biedt.
    • Gebruik een standaard voor veilig programmeren
      Leg uit hoe de SEI CERT C Coding Standard is opgebouwd, lees een regel met de bijbehorende voorbeelden en ken de hoofdregels die de meest voorkomende C-programmeerfouten voorkomen.
    • Ga veilig om met strings en buffers
      Weet altijd hoe groot een buffer is, lees en kopieer strings met functies die rekening houden met die grootte en detecteer afkapping.
    • Valideer invoer en handel fouten af
      Behandel alle gegevens van buitenaf als niet-vertrouwd, converteer tekst naar getallen met volledige foutcontroles en controleer het resultaat van elke bibliotheekaanroep die kan mislukken.
    • Weeg keuzes rond geheugenveiligheid af
      Leg uit wat geheugenveiligheid is, waarom C die mist, wat dat kost en wat C daarvoor biedt, en hoe beveiligingslagen en geheugenveilige programmeertalen het risico verminderen.

Debuggen in C: uitleg

5 hoofdstukken

1

Laat de compiler problemen voor je opsporen

De compiler kan veel problemen aanwijzen voordat je een programma uitvoert. Leer zijn meldingen te lezen als aanwijzingen over je code en kies daarna opties die meer relevante problemen zichtbaar maken. Debuginformatie, optimalisatie, statische analyse en beveiligingsversterking voor productie hebben verschillende taken. Als je die taken duidelijk houdt, kun je een programma bouwen dat je tijdens de ontwikkeling kunt onderzoeken en opnieuw kunt testen in de vorm die je gebruikers uitvoeren.

1.1 Schakel waarschuwingen in en lees compilermeldingen

Waarschuwingen geven je de kans een fout te herstellen voordat invoer die veroorzaakt. Een fout verhindert dat het bouwproces slaagt. Een waarschuwing laat het bouwproces doorgaan, maar meldt iets wat je moet begrijpen: verdachte geldige code, mogelijk ongedefinieerd gedrag of een overtreding van een taalregel die de compiler accepteert.

Kies nuttige controles

Start GCC of Clang met een expliciete taalversie en een brede set waarschuwingen:

clang -std=c17 -Wall -Wextra -Wpedantic -g program.c -o program

-Wall schakelt veel algemene controles in, niet alle waarschuwingen. -Wextra voegt er meer toe, waaronder vergelijkingen van waarden met en zonder teken in C. -Wpedantic meldt veel afwijkingen van de gekozen ISO-standaard; het is geen volledige controle op naleving van de standaard. /W4 van MSVC dient een vergelijkbaar doel op Windows.

Voeg controles toe die passen bij de risico’s in je code:

OptieWat die helpt te vinden
-WconversionImpliciete conversies die een waarde kunnen veranderen
-WshadowEen lokale declaratie die een andere variabele verbergt
-Wformat=2Aanvullende controles van formaatstrings
-WerrorLaat ingeschakelde waarschuwingen het bouwproces stoppen

Bijvoorbeeld: double price = 3.7; int whole = price; laat het gebroken deel weg. -Wconversion maakt die conversie zichtbaar. Als afkapping de bedoeling is en de waarde past, legt (int)price die keuze vast. Als het gebroken deel van belang is, behoud dan een drijvendekommatype. Voor afronden op het dichtstbijzijnde gehele getal heeft lround uit <math.h> een andere taak dan een expliciete typeconversie.

Lees de eerste bruikbare melding

Een diagnostische melding vermeldt normaal gesproken een locatie in de broncode, een melding en de waarschuwingsoptie. Een dakje markeert de relevante expressie; daaropvolgende opmerkingen kunnen een declaratie aanwijzen of een oplossing voorstellen. Lees de hele groep. Als één syntaxisfout veel latere meldingen veroorzaakt, herstel dan de eerste fout en compileer opnieuw voordat je de rest onderzoekt.

Stel dat een programma deze drie regels bevat, waarbij average is gedeclareerd als double:

if (count = 0) { /* report no data */ }
printf("%d\n", average);
int unused;

De toewijzing in de voorwaarde stelt count in op nul, waardoor de voorwaarde onwaar wordt. Als een vergelijking de bedoeling was, gebruik dan count == 0. Haakjes rond de toewijzing toevoegen kan een waarschuwing onderdrukken terwijl de programmeerfout blijft bestaan.

Het formaat %d verwacht een int, maar het argument is een double. Gebruik %f; een niet-passend formaat is ongedefinieerd gedrag, geen verzoek om de waarde te converteren. Verwijder de ongebruikte declaratie als die geen doel dient. Dit zijn drie verschillende oorzaken, dus elke oorzaak heeft een eigen oplossing nodig.

Volg de aanwijzingen

Let goed op niet-geïnitialiseerde waarden, ontbrekende retourinstructies, adressen van lokale variabelen die worden teruggegeven en sizeof op arrayparameters. Door een ontbrekend headerbestand kan een functie ongedeclareerd blijven; recente versies van GCC en Clang wijzen dit standaard af. Voeg niet zomaar een expliciete typeconversie toe om een diagnostische melding te laten verdwijnen. Leg eerst uit welke typen en waarden de bedoeling zijn en zorg daarna dat de code die bedoeling uitdrukt.

Een programma zonder waarschuwingen kan nog steeds een onjuiste lusvoorwaarde of een verkeerde index uit de invoer hebben. Sommige waarschuwingen hangen ook af van optimalisatie en gegevensstroomanalyse, en verschillende compilers vinden verschillende gevallen. Houd de waarschuwingsset ingeschakeld in gewone bouwprocessen en onderzoek daarna het gedrag tijdens de uitvoering met de debugger en testhulpmiddelen.

Belangrijkste punten

  • Kies de C-versie expliciet.
  • -Wall schakelt niet elke waarschuwing in.
  • Herstel de oorzaak van een diagnostische melding voordat je die onderdrukt.
  • Een formaatconversie moet passen bij het type van het argument.
  • Een bouwproces zonder meldingen vereist nog steeds tests tijdens de uitvoering.

1.2 Kies bouwinstellingen voor debuggen, testen en productie

Dezelfde broncode kan worden gebouwd voor gemakkelijke inspectie, intensief testen of efficiënte productie. Bouwinstellingen bepalen welke informatie je kunt zien en welke extra controles worden uitgevoerd. Geef elke versie een duidelijk doel.

Bewaar broncode-informatie

De optie -g voegt namen, typen en koppelingen tussen machine-instructies en broncoderegels toe. Hiermee kan een debugger je code tonen en kunnen sanitizerrapporten locaties aanwijzen. De optie schakelt geen controles tijdens de uitvoering in en schakelt optimalisatie niet uit. Zonder debuginformatie blijven sommige functienamen mogelijk beschikbaar, maar broncoderegels en lokale variabelen zijn veel moeilijker te achterhalen.

Optimalisatie verandert hoe nauw de uitvoering de volgorde van de broncode volgt:

InstellingGebruikelijk doelWat je kunt zien
-O0Eenvoudig debuggenDe volgorde van de broncode is meestal het gemakkelijkst te volgen
-OgDebuggen met lichte optimalisatieSommige omzettingen blijven aanwezig
-O2Efficiënte productieversieVerplaatste bewerkingen en verwijderde variabelen

Tijdens een debugsessie met geoptimaliseerde code kunnen regels worden overgeslagen of kan een variabele als “” worden getoond. De compiler heeft het gedrag van gedefinieerde code behouden, maar bewaart mogelijk niet meer elke tussenwaarde uit de broncode. -O0 vertaalt niet letterlijk elke C-regel naar één machine-instructie.

Bouw voor het doel

Een eenvoudige debugopdracht is:

gcc -std=c17 -Wall -Wextra -Wpedantic -g -O0 program.c -o program-debug

Een testversie voegt sanitizers toe. Een productieversie gebruikt gewoonlijk -O2 en kan NDEBUG definiëren om asserties te verwijderen. Gewone invoervalidatie en foutafhandeling moeten actief blijven wanneer asserties verdwijnen. Test ook de productieconfiguratie: een probleem dat alleen bij optimalisatie zichtbaar is, is een sterke reden om ongedefinieerd gedrag te onderzoeken, waaronder het lezen van niet-geïnitialiseerde waarden en overloop bij gehele getallen met teken.

Bewaar gedeelde opties in een Makefile of een andere bouwconfiguratie. Afzonderlijke uitvoerbestandsnamen voorkomen dat je per ongeluk een oud uitvoerbaar productiebestand uitvoert terwijl je zojuist bewerkte broncode bekijkt. Bouw na elke wijziging in de broncode het uitvoerbare bestand dat je wilt uitvoeren opnieuw.

Voeg analyse en beveiligingsversterking toe

Statische analyse volgt mogelijke paden door de broncode zonder het programma uit te voeren. Probeer gcc -fanalyzer of clang --analyze; scan-build, clang-tidy en cppcheck bieden andere werkwijzen voor analyse. Een analysetool kan opmerken dat een fouttak een pointer tweemaal vrijgeeft, ook als je tests die tak nooit bereiken. Beoordeel de redenering: analyse van mogelijke paden kan valse meldingen opleveren.

Dynamische analyse controleert de daadwerkelijke uitvoering. Sanitizers en Valgrind zien alleen de paden die je uitvoert, dus hun dekking hangt af van je invoer. Combineer beide soorten analyse.

Beveiligingsversterking voor productie beperkt de schade door achtergebleven fouten. -fstack-protector-strong plaatst controles in geselecteerde stackframes om veel overschrijvingen te detecteren. Op systemen die dit ondersteunen, voegt _FORTIFY_SOURCE controles toe aan bibliotheekaanroepen wanneer de objectgroottes kunnen worden bepaald. Dit vereist optimalisatie; niveau 2 is gebruikelijk, terwijl niveau 3 geschikte ondersteuning van de compiler en C-bibliotheek vereist. Dit zijn platformfuncties, geen garanties van ISO C.

Een beveiligd programma kan stoppen wanneer het beschadiging detecteert. De ongeldige bewerking moet nog steeds in de broncode worden hersteld; andere productieopties kunnen een lus die buiten de grenzen gaat niet corrigeren.

Belangrijkste punten

  • Gebruik -g voor debuginformatie op broncodeniveau.
  • Debug met -O0 of -Og en test ook de productieversie.
  • Houd invoercontroles actief wanneer NDEBUG asserties verwijdert.
  • Statische analyse en analyse tijdens de uitvoering vinden verschillende paden en fouten.
  • Bewaar bouwopties in een herhaalbare configuratie.
  • Beveiligingsversterking beperkt de schade door achtergebleven programmeerfouten.
2

Spoor programmeerfouten op en herstel ze met een debugger

Een crash vertelt je waar de uitvoering stopte; de vergissing kan veel eerder zijn gemaakt. Begin met een reproduceerbaar falen en een kleine invoer en gebruik daarna een debugger om het programma op gekozen momenten te onderzoeken. Breekpunten en stapsgewijze uitvoering tonen hoe waarden veranderen. Stackframes, gegevensbreekpunten en voorwaardelijke breekpunten helpen een verkeerde waarde terug te voeren naar de code die die waarde heeft veroorzaakt, zodat elk experiment een specifieke vraag beantwoordt.

2.1 Debug systematisch: reproduceer het probleem, isoleer de oorzaak en toets een hypothese

Falen is het gedrag dat je ziet: een crash, een verkeerd totaal of uitvoer die nooit verschijnt. Een programmeerfout is de vergissing die dat gedrag veroorzaakte. Door die uit elkaar te houden, voorkom je dat je de crashregel verandert voordat je hebt ontdekt hoe het programma daar terechtkwam.

Maak het falen reproduceerbaar

Leg de opdracht, invoer, bouwopties, het verwachte resultaat en het werkelijke resultaat vast. Houd de invoer die het falen veroorzaakt ongewijzigd terwijl je experimenteert. Als een programma alleen bij één bestand faalt, sla dat bestand dan op voordat je het bewerkt. Door het falen te reproduceren kun je beoordelen of een voorgestelde oplossing werkt.

Maak het voorbeeld vervolgens kleiner terwijl het falen blijft optreden. Verwijder niet-gerelateerde records of code en voer het opnieuw uit. Stel dat één record uit 1.000 afzonderlijk een crash veroorzaakt. Door helften te proberen kun je dat record in ongeveer 10 rondes vinden. Als geen van beide helften afzonderlijk faalt, vereist het falen mogelijk een reeks records of een eerder opgebouwde toestand. Behoud die reeks en verwijder kleinere stukken eromheen. Halveren is alleen nuttig als de behouden invoer de oorzaak nog steeds aanspreekt.

Stel één toetsbare vraag

Een leeg veld in een geminimaliseerd bestand suggereert een hypothese: “De parser gebruikt het veld voordat wordt gecontroleerd of het bestaat.” Toets die met een kleine invoer met een leeg veld. Bekijk de teruggegeven pointer of voeg één gerichte controle toe. Verander één ding tegelijk; als je de parser, buffergrootte en invoer samen wijzigt, vertelt succes je niet welke wijziging van belang was.

Herstel de overtreden regel, voer het geminimaliseerde voorbeeld opnieuw uit en voer daarna de oorspronkelijke invoer en bestaande tests opnieuw uit. Bewaar de aanleiding als regressietest, zodat een latere wijziging niet ongemerkt hetzelfde falen kan terugbrengen.

Interpreteer het stoppen

“Segmentation fault” meldt meestal SIGSEGV: het systeem heeft een ongeldige geheugentoegang gestopt. Een nulpointer, een pointer naar vrijgegeven geheugen of een adres buiten het bereik is een veelvoorkomende oorzaak. Een ongeldige toegang kan ook optreden zonder directe crash, dus het uitblijven van een melding zegt op zichzelf weinig.

“Aborted” meldt vaak SIGABRT, veroorzaakt door abort(). Een mislukte assertie of een bibliotheekcontrole die beschadiging van de heap detecteert kan dit veroorzaken. In bash zijn gebruikelijke afsluitstatussen voor signalen 139 voor SIGSEGV en 134 voor SIGABRT; dit zijn shellconventies, geen C-retourwaarden die je programma moet gebruiken.

Observeer zonder uitvoer te verliezen

Een korte diagnostische melding kan de eerste verkeerde waarde zichtbaar maken:

fprintf(stderr, "index=%zu total=%d\n", index, total);

Labels maken de uitvoer leesbaar wanneer dezelfde regel vaak wordt uitgevoerd. stderr is nuttig voor diagnostische meldingen omdat het niet volledig gebufferd is; stdout dat naar een bestand of pipe wordt omgeleid, is normaal gesproken volledig gebufferd, zodat bij een crash nog niet weggeschreven uitvoer verloren kan gaan. Verwijder tijdelijke uitvoerinstructies na het herstel.

Leg een verdachte functie regel voor regel hardop uit. Deze methode met een “badeend” dwingt je om elke bewerking te vergelijken met het gedrag dat je bedoelt. Als een programmeerfout verandert wanneer je uitvoerinstructies toevoegt of de optimalisatie wijzigt, onderzoek dan met sanitizers of er ongedefinieerd gedrag is. Een array groter maken totdat een crash verdwijnt, kan een ongeldige index verbergen zonder die te herstellen.

Belangrijkste punten

  • Bewaar een exacte reproductie voordat je code wijzigt.
  • Minimaliseer de invoer terwijl het falen blijft optreden.
  • Toets één hypothese met één gecontroleerde wijziging of waarneming.
  • Voer een crash terug naar de oorzaak, die eerder kan liggen.
  • Voer het oorspronkelijke voorbeeld opnieuw uit en bewaar een regressietest.

2.2 Voer een programma uit, pauzeer het en doorloop het stapsgewijs met gdb of lldb

Met een debugger kun je een draaiend programma pauzeren en de waarden bekijken die het gedrag verklaren. Gebruik GDB op veel Linux-systemen of LLDB op macOS. Debugknoppen in IDE’s gebruiken dezelfde ideeën: stoppen, uitvoeren, bekijken en stapsgewijs uitvoeren.

Begin met het juiste uitvoerbare bestand

Compileer met -g -O0 en open het resultaat met gdb ./program of lldb ./program. Bij de prompt start run records.txt 3 het programma met twee argumenten. In GDB leidt run < input.txt de standaardinvoer om; gebruik in LLDB process launch -i input.txt. Gebruik quit om af te sluiten.

Stel vóór de uitvoering een breekpunt in. break main in GDB en b main in LLDB stoppen aan het begin van main. Beide accepteren een locatie met bestand en regel, zoals b program.c:12. Volledige LLDB-opdrachten zijn onder meer breakpoint set --name main en breakpoint set --file program.c --line 12.

Een breekpunt stopt normaal gesproken voordat de aangegeven broncode-instructie wordt uitgevoerd. Als de geselecteerde regel een waarde toewijst, druk die dan af nadat je de regel hebt uitgevoerd om de nieuwe waarde te bekijken. Bouw het programma opnieuw na wijzigingen in de broncode: de debugger voert machinecode uit, niet de tekst die in je editor openstaat.

Kies hoe je verdergaat
OpdrachtDoel in GDB en LLDB
next of nVoer de huidige regel uit en behandel aanroepen normaal gesproken als één stap
step of sGa een aangeroepen functie in wanneer de broncode-informatie dat toestaat
finishVoer uit totdat de geselecteerde functie terugkeert
continue of cGa door tot een volgende pauze of het einde van het programma

Andere breekpunten kunnen next of finish onderbreken. Gebruik next voor een bibliotheekaanroep die je niet hoeft te onderzoeken. Als je ongewenste code instapt, brengt finish de besturing terug naar de aanroepende functie.

Bekijk een lus per iteratie

Neem een totaal dat op nul is geïnitialiseerd en deze array:

int a[5] = {4, 8, 15, 16, 23};
for (int i = 0; i <= 5; ++i) {
    sum += a[i];
}

Stop bij de lusinhoud en druk i en sum af vóór de optelling. Na de eerste vijf optellingen zijn de tussentotalen 4, 12, 27, 43 en 66. Bij de volgende pauze geldt i == 5, maar de geldige indices eindigen bij 4. Je hebt de extra iteratie gevonden zonder het ongeldige element te hoeven lezen. Verander de voorwaarde in i < 5, bouw opnieuw en voer opnieuw uit.

print expr of p expr evalueert een expressie in beide debuggers. Begin met veilige waarnemingen zoals p i en p sum; een debuggerexpressie die een ongeldige pointer derefereert, kan zelf een ongeldige leesbewerking proberen. display i van GDB herhaalt de uitvoer bij elke pauze. info locals in GDB en frame variable in LLDB tonen lokale variabelen. Gebruik voor vijf geldige elementen via ptr de opdracht p *ptr@5 van GDB of parray 5 ptr van LLDB.

Je kunt een hypothese toetsen door een waarde tijdens een pauze te veranderen: GDB accepteert set var i = 4, LLDB accepteert expr i = 4. Dit verandert één uitvoering, niet de broncode. Gebruik om breekpunten te beheren info breakpoints en delete 1 van GDB, of breakpoint list en breakpoint delete 1 van LLDB.

Belangrijkste punten

  • Bouw het uitvoerbare bestand met debuginformatie en bouw het opnieuw na wijzigingen.
  • Een broncode-instructie waarbij het programma is gestopt, is normaal gesproken nog niet uitgevoerd.
  • Gebruik next, step, finish en continue om op verschillende manieren verder te gaan.
  • Controleer indices en grenzen voordat je geheugenexpressies evalueert.

2.3 Achterhaal de oorzaak van crashes en onverwachte waarden

Een crash binnen een bibliotheek begint vaak met een ongeldig argument uit je eigen code. De aanroepstapel toont de actieve functieaanroepen. Gebruik die om van de stoplocatie naar de aanroepende functie te gaan waarvan je de waarden kunt verklaren.

Lees stackframes vanaf de huidige aanroep naar buiten

Typ bt in een van beide debuggers. Stackframe #0 is de binnenste actieve aanroep; hogere stackframenummers zijn de aanroepende functies daarvan. Een stackframe geeft de uitvoeringscontext van de functie, waaronder beschikbare argumenten en lokale variabelen. Optimalisatie kan een deel van die informatie verwijderen.

Stel dat find_name(2) geen overeenkomst teruggeeft en de aanroepende functie dit doet:

const char *name = find_name(2);
printf("%zu\n", strlen(name));

Het programma kan binnen strlen stoppen. Zoek in bt het eerste stackframe dat bij je code hoort. Selecteer het met frame 1 in GDB of frame select 1 in LLDB als het stackframe 1 is. Druk name af. Een nulwaarde verklaart waarom de stringbewerking mislukte. De aanroepende functie moet de ontbrekende naam afhandelen voordat die strlen aanroept; de bibliotheek bewerken kan het argument niet herstellen.

Gebruik up en down om tussen aanroepende en aangeroepen functies te bewegen. Als je een stackframe selecteert, verandert wat de debugger toont en naar welke lokale variabelen een expressie verwijst. De uitvoering wordt niet hervat en een aanroep wordt niet ongedaan gemaakt.

Stop waar een waarde verandert

Een breekpunt vraagt: “Wat gebeurt er op deze plek?” Een gegevensbreekpunt vraagt: “Welke bewerking verandert deze waarde?” Als een opgeslagen aantal lang vóór een crash verkeerd wordt, pauzeer dan terwijl het aantal binnen het geldigheidsgebied is en gebruik:

GDB:  watch count
LLDB: watchpoint set variable count

Ga door totdat de debugger stopt bij een wijziging. Bekijk de oude en nieuwe waarde en de broncodelocatie van de schrijfinstructie. Een wijziging kan terecht zijn, dus vergelijk die met de regel van de gegevensstructuur. Hardware biedt slechts een klein aantal gegevensbreekpunten; vier is gebruikelijk op x86-64. De opslag van een lokale variabele heeft alleen betekenis zolang de functie actief is.

Gebruik voor een bekende late iteratie een voorwaardelijk breekpunt. break 20 if i == 999 van GDB pauzeert alleen op regel 20 wanneer de voorwaarde geldt. Het equivalent in LLDB is breakpoint set --line 20 --condition 'i == 999'. Dit bespaart honderden handmatige stappen.

Bekijk een opgeslagen crash

Een geheugendump legt de toestand van een gecrasht proces vast. Waar geheugendumps zijn ingeschakeld, staat ulimit -c unlimited ze toe voor programma’s die vanuit die shell worden gestart. Open een opgeslagen dump met gdb ./program core of lldb ./program -c core, met het uitvoerbare bestand dat de dump heeft gemaakt. Systeeminstellingen bepalen of een dump wordt opgeslagen en waar die terechtkomt; Linux-systemen met systemd kunnen dit via coredumpctl beheren.

Voer bt uit, selecteer je stackframe en bekijk waarden zoals bij een actuele crash. Met de dump kun je het programma niet voortzetten. Als malloc of free beschadiging detecteert, kan de oorspronkelijke ongeldige schrijfbewerking ontbreken in deze aanroepstapel. Reproduceer het probleem met AddressSanitizer om dichter bij die schrijfbewerking te stoppen. Als een lokale waarde willekeurig lijkt, controleer dan of de uitvoering de initialisator heeft bereikt voordat je de debugger de schuld geeft.

Belangrijkste punten

  • Gebruik bt om de actieve aanroepen te volgen.
  • Selecteer een stackframe in je code en bekijk de argumenten die aan de bibliotheek zijn doorgegeven.
  • Gegevensbreekpunten stoppen bij waardeveranderingen; voorwaardelijke breekpunten stoppen bij gekozen toestanden.
  • Met een geheugendump kun je na een crash onderzoek doen.
  • De beschadiging kan zijn ontstaan vóór de aanroepstapel die bij de uiteindelijke crash zichtbaar is.
3

Herken en voorkom ongedefinieerd gedrag

Voor sommige C-bewerkingen garandeert de taal geen resultaat. Ze kunnen in de ene versie lijken te werken en in een andere falen, waardoor gewoon proberen en verbeteren misleidend kan zijn. Leer ongedefinieerd gedrag te onderscheiden van gedocumenteerde platformkeuzes en toegestane variatie. Onderzoek daarna berekeningen, de volgorde van expressies, arraygrenzen en de levensduur van geheugen, en plaats controles voordat een bewerking een van die grenzen overschrijdt.

3.1 Begrijp wat ongedefinieerd gedrag betekent

Ongedefinieerd gedrag betekent dat de C-standaard geen eisen stelt aan wat er gebeurt na een ongeldige bewerking. De standaard belooft geen uitzondering, bruikbare foutmelding of voorspelbaar teruggelopen waarde. Een programma kan crashen, gegevens beschadigen, een aannemelijk antwoord afdrukken of lijken te werken.

Onderscheid drie soorten gedrag
SoortWat de taal toestaatVoorbeeld in C17
OngedefinieerdGeen vereist gedragOverloop bij gehele getallen met teken
Door de implementatie bepaaldEen gedocumenteerde keuze van de implementatieOf een gewone char een teken heeft
Niet-gespecificeerdEen toegestane keuze die niet hoeft te worden gedocumenteerdDe evaluatievolgorde van onafhankelijke functieargumenten

Door de implementatie bepaald gedrag kun je bewust gebruiken wanneer het gedocumenteerde platform deel uitmaakt van het ontwerp van een programma. Ga er voor overdraagbare code niet van uit dat alle implementaties dezelfde keuze maken. Niet-gespecificeerd gedrag is ook geldig: f(a(), b()) kan elk van beide onafhankelijke aanroepen als eerste evalueren. Schrijf code waarvan de correctheid bij die toegestane variatie behouden blijft. De bitbreedte van int en, in C17, het naar rechts verschuiven van een negatief geheel getal met teken zijn andere platformafhankelijke keuzes.

Waarom optimalisatie programmeerfouten zichtbaar maakt

C laat controles van arraygrenzen en optellingen met teken aan de programmeur over. De compiler mag optimaliseren vanuit de aanname dat het programma de regels van de taal volgt. Een poging om overloop te detecteren kan juist de regel overtreden die de code probeert te toetsen:

int will_overflow(int x) {
    return x + 1 < x;
}

Voor elke x waarvoor optelling met teken is gedefinieerd, is x + 1 groter dan x. De compiler kan het resultaat daarom door nul vervangen. Bij INT_MAX is het uitvoeren van de optelling ongedefinieerd gedrag, dus hoeft de compiler de aanroepende functie geen teruggelopen resultaat te geven. Een ander antwoord in een niet-geoptimaliseerde versie maakt dit geen geldige test.

De veilige test vindt vóór de bewerking plaats:

if (x == INT_MAX) {
    /* handle overflow */
} else {
    ++x;
}

Gebruik <limits.h> voor de grenzen van de implementatie. Een vast geprogrammeerd maximum veronderstelt een bepaalde typebreedte; de macro geeft de werkelijke grens aan.

Plaats controles vóór het eerste gebruik

Deze volgorde is ook essentieel voor pointers:

if (p == NULL) {
    return -1;
}
int v = *p;

Als je deze instructies omdraait, kan een nulpointer worden gederefereerd voordat de bescherming werkt. Zodra code p derefereert, kan een optimizer redeneren dat het gedefinieerde pad al vereist dat p geen nulpointer is en een latere controle op nul verwijderen.

Andere opties instellen, een uitvoerinstructie toevoegen of een variabele verplaatsen kan het zichtbare verschijnsel van ongedefinieerd gedrag veranderen. Een geslaagde uitvoering toont alleen aan wat die versie met die invoer deed. Om de code te herstellen, bepaal je welke bewerking geen gedefinieerde betekenis heeft en welke voorwaarde daarvoor moet gelden. Waarschuwingen kunnen sommige gevallen aanwijzen; statische analyse kan mogelijke paden onderzoeken; ASan en UBSan controleren veel uitgevoerde bewerkingen. Programmeerregels bieden de bijbehorende patronen op broncodeniveau, zoals een index of een rekenkundige grens controleren vóór gebruik.

Belangrijkste punten

  • Ongedefinieerd gedrag garandeert geen crash en geen stabiel resultaat.
  • Door de implementatie bepaalde keuzes moeten worden gedocumenteerd.
  • Niet-gespecificeerde keuzes blijven binnen de taalregels.
  • Optimizers mogen erop vertrouwen dat overloop bij gehele getallen met teken nooit voorkomt in gedefinieerde code.
  • Controleer een pointer of rekenkundige grens vóór de risicovolle bewerking.

3.2 Herken ongedefinieerd gedrag in berekeningen en expressies

Een berekening kan al misgaan voordat je de kans hebt het resultaat te bekijken. Controleer de operanden zolang hun waarden nog geldig zijn en weet welk type elke bewerking uitvoert.

Grenzen voor getallen met en zonder teken

Overloop bij gehele getallen met teken is ongedefinieerd. Bij een 32-bits int is INT_MAX 2.147.483.647; er één bij optellen is geen overdraagbare manier om een negatief getal te krijgen. Ook INT_MIN / -1, -INT_MIN en te grote vermenigvuldigingen gaan mis als het wiskundige resultaat niet past.

Rekenen met gehele getallen zonder teken loopt modulair terug binnen het bereik van het type. Bij een 32-bits unsigned int is UINT_MAX + 1u nul en 0u - 1u 4.294.967.295. Dit is gedefinieerd, maar kan een grootteberekening toch onjuist maken. Een achterwaartse lus met een waarde zonder teken die i >= 0 gebruikt, bereikt nooit een negatieve stopwaarde.

Controleer vóór het optellen voor een algemene optelling met teken:

if ((b > 0 && a > INT_MAX - b) ||
    (b < 0 && a < INT_MIN - b)) {
    /* report overflow */
} else {
    result = a + b;
}

De aftrekkingen in deze tests blijven binnen het bereik omdat elke aftrekking wordt beschermd door het teken van b. Eerst de som berekenen en die daarna vergelijken is te laat. Een breder type helpt alleen als elk tussenresultaat erin past: converteer een operand vóór de bewerking. C23 biedt ckd_add in <stdckdint.h>; GCC en Clang bieden __builtin_add_overflow.

Deling, bitverschuivingen en conversies

Wijs nul af vóór een geheeltallige deling of restberekening. Wijs ook het minimum van een type met teken gedeeld door min één af wanneer het resultaat niet past. Drijvendekommasystemen volgens IEEE 754 leveren bij deling door nul vaak oneindig of NaN op, waardoor ongeldige numerieke resultaten zich kunnen verspreiden zonder een onderbreking zoals bij gehele getallen.

Gebruik voor bitbewerkingen operanden zonder teken. Het aantal posities voor de verschuiving moet niet-negatief en kleiner zijn dan de bitbreedte van de gepromoveerde linkeroperand. Bij 32-bits operanden is verschuiven met 32 posities ongedefinieerd. In C17 is een negatieve waarde met teken naar links verschuiven ongedefinieerd; 1 << 31 is ook ongedefinieerd voor een 32-bits int, terwijl 1u << 31 geldig is voor een 32-bits unsigned int.

Bij een conversie van een drijvendekommawaarde naar een geheel getal moet de afgekaptе waarde passen, ook als het doeltype geen teken heeft. (unsigned)-1.0 is ongedefinieerd. Een conversie van een geheel getal naar een type zonder teken brengt de waarde daarentegen terug binnen het bereik van het doeltype: (unsigned)-1 wordt UINT_MAX. Een conversie van een geheel getal buiten het bereik naar een type met teken is in C17 door de implementatie bepaald. Controleer bereiken vóór een conversie naar een smaller type; -Wconversion helpt impliciete veranderingen zichtbaar te maken.

Maak de evaluatievolgorde onschadelijk

printf("%d %d\n", i, ++i); leest en verandert i zonder de vereiste ordening. i = i++ + 1; en a[i] = i++; bevatten ook niet-geordende bewerkingen. Splits de bewerkingen op in afzonderlijke instructies met een expliciete bedoelde volgorde.

Initialiseer automatische variabelen voordat je ze leest: int sum = 0; maakt sum += x; betekenisvol. Globale en statische variabelen beginnen op nul. De eis van C23 voor een tweecomplementvoorstelling definieert overloop bij gehele getallen met teken niet; overdraagbare code heeft nog steeds de controles nodig.

Belangrijkste punten

  • Controleer berekeningen met teken voordat je het resultaat berekent.
  • Modulaire terugloop bij getallen zonder teken is gedefinieerd, maar kan grootten en lussen onjuist maken.
  • Controleer delingsoperanden en aantallen posities voor bitverschuivingen.
  • Conversie van een drijvendekommawaarde naar een type zonder teken verschilt van conversie van een geheel getal naar een type zonder teken.
  • Orden wijzigingen expliciet en initialiseer waarden vóór gebruik.
  • Ook C23 laat overloop bij gehele getallen met teken ongedefinieerd.

3.3 Herken ongedefinieerd gedrag bij pointers, arrays en geheugengebruik

Een pointer heeft meer nodig dan een aannemelijk adres. Het object moet bestaan, de toegang moet binnen de grenzen ervan blijven en de bewerking moet voor dat object zijn toegestaan. Deze controles verklaren zowel directe crashes als beschadiging die veel later wordt ontdekt.

Blijf binnen het object

Een array van n elementen heeft indices van nul tot en met n - 1. Een pointer direct voorbij het laatste element kan het einde van een lus markeren, maar je mag die niet derefereren. i <= n bezoekt één element te veel. CERT ARR30-C behandelt deze grensfouten.

Een schrijfbewerking buiten de grenzen kan terechtkomen in toegankelijk geheugen dat bij een ander object hoort. Het programma kan doorgaan totdat een beschadigd aantal, een beschadigde pointer of een beschadigd record van de allocator wordt gebruikt. Een latere crash in free wijst de oorspronkelijke schrijfbewerking niet aan. ASan kan bij de schrijfbewerking stoppen wanneer de controles de grensoverschrijding detecteren.

Maak van tekens een geldige string

Een C-string eindigt met '\0'. De opslag heeft één byte meer nodig dan het aantal tekens. C accepteert char s[5] = "hello";, maar alle vijf bytes bevatten letters, zodat er geen afsluitend nulteken overblijft. Als je de array aan strlen of %s doorgeeft, kan er voorbij de array worden gelezen. Gebruik voldoende ruimte en zorg dat er daadwerkelijk een afsluitend nulteken aanwezig is. STR31-C gaat over capaciteit; STR32-C gaat over het doorgeven van een string met afsluitend nulteken.

Een stringliteral is niet beschrijfbaar. Na char *p = "hi"; is p[0] = 'H'; ongedefinieerd. Gebruik char p[] = "hi"; voor een beschrijfbare kopie, of const char *p om pogingen om de literal te veranderen zichtbaar te maken voor de compiler. Dit is STR30-C.

Respecteer levensduur en eigenaarschap

Controleer op NULL voordat je het resultaat van een geheugenallocatie, het openen van een bestand of een zoekactie derefereert. Wanneer een blok wordt vrijgegeven, verliest elke pointer naar een plaats daarin toegang tot een bestaand object. Lees of beschrijf het blok niet en geef het niet opnieuw vrij. Geef alleen de juiste pointer vrij die door een dynamische geheugenallocatie is teruggegeven, nooit een lokale array of een pointer naar een positie binnen een object. free(NULL) is onschadelijk, maar één pointer na vrijgave op nul zetten herstelt andere aliassen niet.

Een functie die het adres van een lokale array teruggeeft, geeft een ongeldige pointer naar een niet meer bestaand object terug:

char *greeting(void) {
    char text[16] = "Welcome";
    return text;
}

De levensduur van de array eindigt wanneer de functie terugkeert. Laat de aanroepende functie een buffer en capaciteit doorgeven om te vullen, of reserveer een resultaat waarvan het eigenaarschap overgaat op de aanroepende functie. Een geslaagde afdruk direct na de terugkeer kan de levensduur van de oude array niet verlengen.

Reserveer bytes voor elk element

malloc(n) vraagt bytes aan, geen n objecten van het type waarnaar wordt verwezen. De gebruikelijke expressie op basis van elementen is n * sizeof *a. Voorkom eerst dat dat product modulair terugloopt:

if (n > SIZE_MAX / sizeof *a) {
    /* reject the requested size */
}

Neem <stdint.h> op voor SIZE_MAX en <stdlib.h> voor allocatiefuncties. Als de grootte geldig is, reserveer je het geheugen, controleer je de teruggegeven pointer en schrijf je elke waarde voordat je die leest. malloc laat geheugen niet-geïnitialiseerd achter. calloc(n, sizeof *a) vult de opslag met nullen en gangbare bibliotheken wijzen producten af die overlopen. Een groottecontrole, een controle van het allocatieresultaat en een geldige index lossen verschillende problemen op; alle drie zijn van belang.

Belangrijkste punten

  • Derefereer pointers alleen binnen de grenzen van een bestaand object.
  • Reserveer ruimte voor het afsluitende nulteken van de string en sla het op.
  • Wijzig geen stringliterals.
  • Als je een geheugenallocatie vrijgeeft, wordt toegang via alle aliassen ongeldig.
  • Geef opslag terug met een levensduur die de aanroepende functie kan gebruiken.
  • Controleer de vermenigvuldiging met het aantal elementen en het allocatieresultaat.
4

Spoor tijdens de uitvoering fouten op met asserties en sanitizers

Je kunt een testversie meer laten controleren dan alleen de uiteindelijke uitvoer. Asserties controleren de aannames die je eigen code belooft te handhaven, terwijl sanitizers bewerkingen zoals geheugentoegang en berekeningen controleren terwijl ze plaatsvinden. Leer elk rapport te lezen, kies een hulpmiddel voor de fout die je vermoedt en voer invoer uit die de risicovolle paden bereikt. Een nuttige controleroutine verbindt een gedetecteerde fout met een oplossing en een blijvende test.

4.1 Leg aannames vast met asserties

Een assertie legt iets vast wat waar hoort te zijn omdat je eigen code correct is. Een verborgen geschonden aanname wordt zo een stop dicht bij de plaats waar die aanname van belang is.

Controleer een programmeercontract

Neem <assert.h> op en schrijf assert(condition);. Bij een ware voorwaarde gaat de uitvoering door. Een onware voorwaarde drukt diagnostische informatie af naar stderr en roept abort() aan. De melding vermeldt de expressie, de broncodelocatie en de functie; de exacte formulering hangt af van het platform.

Voor een interne hulpfunctie voor gemiddelden waarvan de aanroepende functie geldige argumenten belooft:

double average(const int *a, size_t n) {
    assert(a != NULL);
    assert(n > 0);
    /* compute the average */
}

Een onbedoelde aanroep met nul elementen faalt bij de geschonden belofte. Onder een debugger toont bt de assertie en de code voor het afbreken, gevolgd door de aanroepen in je code. Selecteer het stackframe van de aanroepende functie om te bekijken hoe die nul heeft doorgegeven.

Contracten komen op verschillende punten voor:

SoortWat het vastlegtVoorbeeld
PreconditieVereist bij binnenkomstEen niet-lege invoerarray
PostconditieGegarandeerd na de bewerkingEen array is gesorteerd
InvariantBlijft gelden tussen stappenEen opgeslagen aantal komt overeen met de structuur

Een assertie is uitvoerbare documentatie: die benoemt de eigenschap en controleert die tijdens een testuitvoering. Kies expressies die de toestand bekijken zonder die te veranderen.

Houd noodzakelijke bewerkingen buiten asserties

Als je NDEBUG definieert voordat je <assert.h> opneemt, meestal met -DNDEBUG, wordt assert uitgeschakeld. De expressie wordt niet geëvalueerd. Dit veroorzaakt een ernstige programmeerfout als de expressie noodzakelijke bewerkingen uitvoert:

assert(++count <= MAX);

De incrementatie verdwijnt in een versie met NDEBUG. Schrijf de handeling afzonderlijk:

++count;
assert(count <= MAX);

Dezelfde regel sluit het openen van bestanden, reserveren van geheugen of sluiten van bestanden binnen een assertie uit. Die bewerkingen moeten in alle configuraties plaatsvinden. Mislukkingen daarvan vereisen gewone foutafhandeling.

Handel verwachte fouten normaal af

Een ontbrekend invoerbestand, ongeldige gebruikerstekst en uitgeput geheugen kunnen bij correct gebruik van een programma voorkomen. Controleer ze met if, meld de fout en keer terug of ruim op. Een interne hulpfunctie kan een gevalideerd bereik met een assertie controleren, maar de openbare invoergrens moet dat bereik afdwingen met controles die in productieversies blijven bestaan.

Test bijvoorbeeld altijd fclose(f) == EOF in de gewone besturingsstroom wanneer je een uitvoerbestand sluit. Gebufferde uitvoer kan tijdens het sluiten mislukken; als je de aanroep of de controle verwijdert, kunnen verloren gegevens verborgen blijven.

Controleer vaste feiten tijdens het compileren

Een statische assertie test een constante expressie tijdens het compileren. In C17 biedt <assert.h> static_assert als macro voor _Static_assert uit C11:

static_assert(sizeof(int) == 4, "requires a 4-byte int");

Een onware voorwaarde laat het bouwproces mislukken. De assertie kan geen door een gebruiker ingevoerd aantal controleren dat pas tijdens de uitvoering bekend is. C23 maakt static_assert tot een sleutelwoord en staat toe de melding weg te laten. Gebruik dit onderscheid om elke controle te plaatsen waar de benodigde informatie beschikbaar is.

Belangrijkste punten

  • Asserties controleren interne programmeerbeloften.
  • Een mislukte assertie tijdens de uitvoering meldt de locatie en breekt het programma af.
  • Houd neveneffecten buiten assertie-expressies.
  • Gebruik gewone foutafhandeling voor ongeldige invoer en fouten bij hulpbronnen.
  • NDEBUG schakelt asserties tijdens de uitvoering uit.
  • Statische asserties controleren constante expressies tijdens het compileren.

4.2 Spoor geheugenfouten op met AddressSanitizer

AddressSanitizer, meestal ASan genoemd, voegt controles toe aan gecompileerde geheugenbewerkingen. Het hulpmiddel kan ongeldige toegang melden op het moment dat die plaatsvindt, voordat beschadigde gegevens een crash op een andere plek veroorzaken.

Bouw en koppel met de controles

Gebruik GCC of Clang om met ASan te compileren en te koppelen:

clang -std=c17 -g -fsanitize=address -fno-omit-frame-pointer program.c -o program-asan

-g levert broncodelocaties. Het behouden van framepointers helpt bij aanroepoverzichten. Instrumenteer bij een project met meerdere bestanden de relevante bronbestanden en gebruik de sanitizeroptie ook bij de laatste koppelstap. MSVC biedt /fsanitize=address voor ondersteunde Windows-versies.

ASan detecteert veel grensoverschrijdingen van heapblokken, arrays op de stapel en globale arrays. Het detecteert ook gebruik na vrijgave, dubbele vrijgave, ongeldige vrijgave en ondersteunde fouten door gebruik na het einde van een geldigheidsgebied. Op Linux bevat de gebruikelijke uitvoeringsomgeving van ASan lekcontrole bij het afsluiten. De meegeleverde Clang van Apple op macOS biedt die lekdetectie niet; LLVM-gereedschapsketens en platformondersteuning verschillen.

Volg een rapport naar de oorzaak

Neem een met succes gereserveerde array, met een int van 4 bytes:

int *a = malloc(10 * sizeof *a);
for (int i = 0; i <= 10; ++i) {
    a[i] = i;
}

De geheugenallocatie bevat 40 bytes. De indices nul tot en met negen gebruiken die bytes. Index tien begint direct daar voorbij. ASan kan “heap-buffer-overflow”, “WRITE of size 4” en een locatie nul bytes na een gebied van 40 bytes melden. Het overzicht van de schrijfbewerking wijst de toewijzing aan; het allocatieoverzicht wijst de malloc aan die de grenzen van het blok vastlegde.

Lees rapporten in deze volgorde:

  1. Bepaal de foutcategorie en of de toegang een lees- of schrijfbewerking was.
  2. Zoek in het toegangsoverzicht het eerste stackframe in je eigen code.
  3. Vergelijk de toegang met het gereserveerde gebied en de elementgrootte.
  4. Zoek bij een levensduurfout ook de overzichten van de allocatie en de vrijgave.

Hier is de oplossing i < 10. Als je nog een element reserveert en de verkeerde stopregel behoudt, verberg je het verschil tussen het bedoelde aantal en de lus. ASan stopt standaard bij de eerste gedetecteerde fout. Herstel die, bouw opnieuw en voer opnieuw uit.

Begrijp de grenzen van het controlehulpmiddel

ASan houdt toegestaan geheugen bij met schaduwgeheugen. Verboden geheugenzones omringen ondersteunde geheugenallocaties en arrays. Vrijgegeven blokken blijven vóór hergebruik een tijd in quarantaine, wat helpt om achterhaalde toegangen zichtbaar te maken. Een schrijfbewerking voorbij een arrayveld naar een ander veld van dezelfde struct kan binnen een groter geldig gebied blijven en aan detectie ontsnappen.

ASan controleert het gewoon lezen van niet-geïnitialiseerde waarden niet. Memcheck van Valgrind kan dat gebruik en veel heapfouten zonder opnieuw compileren vinden, maar is doorgaans ongeveer 20 tot 30 keer zo langzaam. ASan vertraagt doorgaans ongeveer twee keer en gebruikt extra geheugen. Combineer een ASan-versie niet met Valgrind, MemorySanitizer of ThreadSanitizer. Voer bewust grensgevallen als invoer uit: een fout op een niet-uitgevoerd pad kan niet in het rapport verschijnen.

Belangrijkste punten

  • Compileer en koppel met -fsanitize=address en debuginformatie.
  • Lees het toegangsoverzicht samen met de overzichten van allocatie of vrijgave.
  • Herstel de eerste gemelde oorzaak, bouw opnieuw en voer opnieuw uit.
  • ASan detecteert het gewoon lezen van niet-geïnitialiseerde waarden niet.
  • Test grensgevallen als invoer om risicovolle geheugentoegangen te bereiken.

4.3 Detecteer ongedefinieerd gedrag met UBSan en ontwikkel een vaste controleroutine

Een geheugencontrolehulpmiddel dekt niet elke ongeldige C-bewerking. UndefinedBehaviorSanitizer, of UBSan, instrumenteert veel bewerkingen met berekeningen, pointers en arrays. Combineer het met ASan zodat een testuitvoering meer dan één soort fout kan detecteren.

Maak rekenfouten zichtbaar

Bij een 32-bits int levert het optellen van twee waarden van 2.000.000.000 een totaal van 4.000.000.000 op. Dat resultaat is groter dan INT_MAX. Een UBSan-rapport wijst overloop bij gehele getallen met teken aan bij de optelling en noemt de operanden. Het afdrukken van een negatief antwoord dat op terugloop lijkt zonder het controlehulpmiddel zou de bewerking niet geldig maken.

Gebruik voor het totaal een type dat het geaccepteerde bereik kan voorstellen, of wijs een optelling af voordat die overloopt. Het al overgelopen resultaat converteren naar long long is te laat; de bewerking zelf moet een geschikt type gebruiken.

Een gebruikelijke testopdracht is:

clang -std=c17 -Wall -Wextra -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=undefined program.c -o program-check

UBSan staat normaal gesproken herstel na veel meldingen toe, zodat latere code kan doorgaan. -fno-sanitize-recover=undefined laat die fouten de test stoppen met een foutstatus. Dat helpt geautomatiseerde controles een programmeerfout te detecteren, ook wanneer de uiteindelijke afgedrukte uitvoer juist lijkt.

Kies het hulpmiddel bij de fout
HulpmiddelHoofddoelBelangrijke beperking
UBSanVeel ongedefinieerde bewerkingen met berekeningen, bitverschuivingen, nul, uitlijning en arraysDetecteert niet elke soort ongedefinieerd gedrag
ASanVeel fouten bij grenzen en levensduurVolgt gewone niet-geïnitialiseerde waarden niet
MemorySanitizerGebruik van niet-geïnitialiseerd geheugenHeeft geïnstrumenteerde afhankelijkheden nodig voor betrouwbare resultaten
ThreadSanitizerDataracesVereist een afzonderlijke versie, los van de ASan-versie

Modulaire terugloop bij gehele getallen zonder teken is gedefinieerd, dus de groep undefined meldt die niet. Clang heeft een afzonderlijke controle -fsanitize=unsigned-integer-overflow voor code waarin terugloop op een programmeerfout wijst. Leid hieruit niet af dat elk ongewenst geheelgetalresultaat ongedefinieerd gedrag is.

MemorySanitizer is een hulpmiddel van Clang dat vooral op Linux wordt gebruikt; niet-geïnstrumenteerde bibliotheken kunnen de tracering ervan verstoren. ThreadSanitizer detecteert conflicterende toegangen door threads zonder synchronisatie, met minstens één schrijfbewerking. De toegangen hoeven niet op exact hetzelfde moment plaats te vinden. Geen van beide hulpmiddelen kan in één versie met ASan worden gecombineerd.

Maak van controleren een routine

Houd waarschuwingen ingeschakeld en los ze op. Bouw met ASan en UBSan en voer vervolgens lege invoer, invoer met één element, volle buffers, ongeldige tekst en waarden dicht bij de geaccepteerde grenzen uit. Voer statische analyse uit om paden te onderzoeken die de tests kunnen missen en herhaal de controles automatisch wanneer code verandert. Voer ook de productieconfiguratie uit, waarin optimalisatie en assertie-instellingen verschillen.

Fuzzing levert veel gegenereerde of gewijzigde invoer. libFuzzer en AFL++ kunnen ongebruikelijke paden in parsers blootleggen; sanitizers zetten een veroorzaakte ongeldige bewerking om in een bruikbaar rapport. Bewaar invoer die een fout vindt, minimaliseer die zonder het falen te verliezen, herstel de oorzaak en bewaar de invoer als regressietest. Een uitvoering zonder meldingen is bewijs over de uitgevoerde invoer, geen garantie voor alle mogelijke uitvoeringen.

Belangrijkste punten

  • Gebruik UBSan voor veel fouten in berekeningen en expressies.
  • Laat UBSan-fouten geautomatiseerde tests stoppen wanneer herstel ze zou verbergen.
  • Modulaire terugloop bij getallen zonder teken vereist een afzonderlijke controle wanneer die ongewenst is.
  • Gebruik afzonderlijke versies voor MemorySanitizer of ThreadSanitizer.
  • Combineer grenswaardetests, fuzzing, statische analyse en regressie-invoer.
  • De dekking van sanitizers hangt af van de uitvoering.
5

Schrijf veiligere C-code

Veiliger C programmeren begint waar gegevens een programma binnenkomen en gaat door bij elke grootteberekening, kopie, geheugenallocatie en elk foutpad. Een programmeerstandaard zet terugkerende fouten om in concrete controles die je tijdens het schrijven van code kunt toepassen. Leer buffergroottes te behouden, volledige numerieke invoer te valideren en hulpbronnen onder duidelijk eigenaarschap te houden. Weeg deze verantwoordelijkheden vervolgens af tegen de controle over geheugen die C biedt en de bescherming van geheugenveilige programmeertalen.

5.1 Gebruik een standaard voor veilig programmeren

Een standaard voor veilig programmeren verzamelt concrete regels die terugkerende fouten voorkomen. Die geeft een beoordelaar nuttigere vragen dan “Ziet dit er veilig uit?” Elke regel verbindt een risicovolle bewerking met de voorwaarde of het ontwerp dat nodig is om die veilig te maken.

Lees een regel om de reden te begrijpen

De SEI CERT C Coding Standard komt van het Software Engineering Institute van Carnegie Mellon University. Naast de boekeditie uit 2016 bestaat een online versie die verder wordt ontwikkeld. De standaard onderscheidt regels, waarvan overtreding waarschijnlijk fouten veroorzaakt, en aanbevelingen die de betrouwbaarheid en beveiliging verbeteren.

Identifiers combineren een categorie, een getal en -C. Regels gebruiken de nummers 30–99; aanbevelingen gebruiken 00–29. Zo gaat INT32-C over overloop bij gehele getallen met teken, terwijl DCL00-C een aanbeveling over declaraties is. Categorieën omvatten declaraties (DCL), expressies (EXP), gehele getallen (INT), drijvende komma (FLP), arrays (ARR), strings (STR), geheugen (MEM), invoer en uitvoer (FIO), omgeving (ENV), fouten (ERR) en overige onderwerpen (MSC).

Een regelpagina legt normaal gesproken de fout uit, toont code die niet aan de regel voldoet, presenteert oplossingen die eraan voldoen en beoordeelt het risico. Lees de aannames van elke oplossing. Een vervangende functie is alleen nuttig wanneer het gedrag daadwerkelijk aan de eis voldoet.

Pas een regel toe op een gegevenspad

FIO30-C gaat over formaatstrings. Deze code behandelt gegevens van buitenaf als een opmaakprogramma:

printf(user_name);

Een gebruikersnaam met formaatconversies laat printf zoeken naar argumenten die niet zijn doorgegeven; %n kan een schrijfbewerking in het geheugen veroorzaken. Houd het formaat onder controle van het programma:

printf("%s", user_name);

Als alternatief drukt fputs(user_name, stdout) de tekst af zonder opmaakverwerking. -Wformat=2 bevat controles die onveilige formaten helpen vinden. De oplossing verandert hoe gegevens worden geïnterpreteerd, niet alleen hoe lang de string mag zijn.

Maak een nuttige controlelijst

Vraag bij geheugen of indices binnen objecten blijven (ARR30-C), stringopslag het afsluitende nulteken bevat (STR31-C), vrijgegeven opslag nooit wordt benaderd (MEM30-C), geheugenallocaties waarvan de code eigenaar is worden vrijgegeven (MEM31-C), alleen geldige geheugenallocaties worden vrijgegeven (MEM34-C) en genoeg bytes worden gereserveerd (MEM35-C).

Controleer bij expressies op onbedoelde modulaire terugloop bij getallen zonder teken (INT30-C), overloop bij getallen met teken (INT32-C), ongeldige deling (INT33-C), het lezen van niet-geïnitialiseerde waarden (EXP33-C) en het derefereren van nulpointers (EXP34-C). Handel bij bibliotheekaanroepen gemelde fouten (ERR33-C) en conversiefouten (ERR34-C) af. Vermijd system() volgens ENV33-C, omdat een shell de invoer ervan interpreteert.

Risicotabellen beoordelen ernst, waarschijnlijkheid en herstelkosten van 1 tot en met 3; een hogere kostenscore betekent goedkoper herstel. Het product is de prioriteit, van 1 tot en met 27. Niveaus groeperen prioriteiten in L1 (12–27), L2 (6–9) en L3 (1–4). Dit helpt herstelwerk te ordenen zonder fouten met lagere prioriteit onschadelijk te verklaren.

Gebruik regels tijdens codebeoordeling en gebruik bevindingen van analysetools die aan hun identifiers zijn gekoppeld. MISRA C is een andere standaard die wordt gebruikt bij veiligheidskritiek ingebed werk. Regels helpen fouten te voorkomen, terwijl waarschuwingen, tests en sanitizers controleren of de implementatie ze daadwerkelijk volgt. Alleen identifiers onthouden kan geen ontbrekend afsluitend nulteken of overgeslagen fouttak detecteren.

Belangrijkste punten

  • Lees het mechanisme achter de fout en de oplossing die aan de regel voldoet samen.
  • CERT-identifiers onderscheiden categorieën, regels en aanbevelingen.
  • Houd gegevens van buitenaf uit formaatstrings.
  • Pas concrete controles toe op grenzen, levensduur, berekeningen en fouten.
  • Gebruik risicobeoordelingen om werk te ordenen en verifieer de code daarna met hulpmiddelen.

5.2 Ga veilig om met strings en buffers

Veilig omgaan met strings vereist een geldige bron, een bekende capaciteit van de bestemming en een beslissing over tekst die niet past. Een functie met een groottelimiet voorkomt sommige overschrijvingen, maar je moet nog steeds controleren of de hele bewerking is geslaagd.

Geef de capaciteit mee met de pointer

sizeof meet het type van de operand. Voor een lokale char buf[20] levert dit 20 bytes op. In void fill(char buf[]) wordt de parameter omgezet naar een pointer; sizeof buf meet die pointer, niet de array van de aanroepende functie. Geef een capaciteitsparameter door, zoals in fill(buf, sizeof buf), terwijl de aanroepende functie nog over het arraytype beschikt.

Een string met vijf tekens heeft zes bytes nodig, inclusief '\0'. De capaciteit telt elke beschrijfbare byte mee; de stringlengte sluit het afsluitende nulteken uit. Daarom reserveert malloc(strlen(src)) één byte te weinig voor een kopie.

Lees een regel binnen de grenzen

Gebruik fgets(buf, sizeof buf, stdin) en controleer de retourwaarde voordat je de inhoud gebruikt. De functie slaat hoogstens de capaciteit min één tekens op, gevolgd door een afsluitend nulteken. Een regelafbrekingsteken blijft behouden wanneer het past. Na een geslaagde leesbewerking verwijdert buf[strcspn(buf, "\n")] = '\0'; een opgeslagen regelafbrekingsteken.

Als een lange regel de buffer vult, blijven ongelezen tekens over voor de volgende aanroep. Beslis of je de rest afwijst en uitleest, meer leest of afkapping accepteert. Een regelafbrekingsteken verwijderen bewijst niet dat je de volledige regel hebt gelezen. gets kan geen capaciteit ontvangen en is in C11 verwijderd; gebruik die functie nooit.

Voor één woord in char name[20] beperkt %19s in scanf het aantal opgeslagen tekens en blijft ruimte over voor het afsluitende nulteken. Controleer of het verwachte aantal conversies is geslaagd. Lees voor niet-vertrouwde numerieke tekst bij voorkeur een volledige regel, gevolgd door een gecontroleerde strtol; %d heeft ongedefinieerd gedrag wanneer de geconverteerde waarde niet kan worden weergegeven, ook als je het door scanf teruggegeven aantal controleert.

Detecteer een verkorte kopie

strcpy en strcat kennen de capaciteit van de bestemming niet. strncpy kan het resultaat zonder afsluitend nulteken achterlaten. Bij een positieve capaciteit sluit snprintf de opgeslagen tekst af en meldt het hoeveel tekens de volledige uitvoer nodig had:

char dst[8];
int r = snprintf(dst, sizeof dst, "%s", "debugging");
if (r < 0) {
    /* report encoding failure */
} else if ((size_t)r >= sizeof dst) {
    /* report truncation */
}

De opgeslagen string is “debuggi” en r is 9. De volledige uitvoer heeft tien bytes nodig inclusief het afsluitende nulteken. Controleer of het resultaat negatief is voordat je het naar size_t converteert. Een passend resultaat vereist dat r kleiner is dan de capaciteit.

Stem de bewerking af op de opslag

Gebruik memmove wanneer bron en bestemming overlappen; memcpy met overlap is ongedefinieerd. strlcpy is beschikbaar op BSD-systemen, macOS en vanaf glibc 2.38, maar is geen ISO C. Functies uit bijlage K zijn optioneel en in veel bibliotheken niet beschikbaar.

Houd gebruikersgegevens uit het formaat zelf. Stem elke conversie af op het argument: %d voor int, %ld voor long, %zu voor size_t, %f voor double en %s voor een string met afsluitend nulteken. %p vereist een void *, dus geef een geschikte pointerconversie door. Een breedtelimiet kan een niet-passend argumenttype niet herstellen.

Belangrijkste punten

  • Geef de capaciteit van de bestemming samen met een pointer door.
  • Reserveer één byte voor het afsluitende nulteken van de string.
  • Handel onvolledige invoerregels expliciet af.
  • Controleer snprintf op mislukking en afkapping.
  • Gebruik memmove voor overlappende geheugenbereiken.
  • Stem elke formaatconversie af op het type van het argument.

5.3 Valideer invoer en handel fouten af

Gegevens van buitenaf kunnen syntactisch geldig zijn en toch onveilig voor de bewerking die je wilt uitvoeren. Valideer de lengte, het volledige formaat en het bruikbare bereik voordat je de gegevens gebruikt als index, allocatiegrootte of aantal lusiteraties.

Verwerk het hele getal

atoi biedt geen betrouwbare manier om een geldige nul te onderscheiden van ongeldige tekst, en een resultaat dat niet kan worden weergegeven veroorzaakt ongedefinieerd gedrag. strtol biedt een eindpointer en een bereikfout. Na het lezen van een volledige regel en het verwijderen van het regelafbrekingsteken kan een strikte parser voor decimale getallen dit gebruiken:

errno = 0;
char *end;
long v = strtol(text, &end, 10);
if (end == text || *end != '\0' || errno == ERANGE) {
    /* reject the numeric text */
} else if (v < 0 || v > 150) {
    /* reject this application's range */
} else {
    int age = (int)v;
    /* use age */
}

Neem <errno.h> en <stdlib.h> op. De eerste test onderscheidt drie fouten: geen cijfers, tekst aan het einde en een waarde buiten het bereik van long. De controle van het toepassingsbereik komt vóór de conversie naar het smallere type int. strtol accepteert voorafgaande spaties en een optioneel teken; bepaal of de grammatica van de toepassing die toestaat.

Voor “42abc” kan de conversie 42 teruggeven, maar end wijst naar de resterende letters. Voor “abc” worden geen cijfers verwerkt. Voor een te groot decimaal geheel getal wordt errno gelijk aan ERANGE. Alleen een teruggegeven getal dat niet nul is, bewijst noch dat de conversie volledig is, noch dat de waarde binnen een veilig bereik valt.

Behoud eigenaarschap bij een fout

Elke bewerking die kan mislukken, vereist een reactie. Controleer allocatieresultaten, fopen, fgets, aantallen geslaagde conversies en of uitvoer is voltooid. Als fgets NULL teruggeeft, kan dat het einde van de invoer of een leesfout betekenen. fclose kan melden dat een gebufferde schrijfbewerking is mislukt. Gebruik foutinformatie die bij de functie past; veel bibliotheekaanroepen stellen errno in, dat perror of strerror kan omzetten in een leesbare diagnostische melding.

Bescherm bij het wijzigen naar een positieve grootte de grootteberekening en de oorspronkelijke geheugenallocatie:

if (new_n == 0) {
    free(a);
    a = NULL;
} else if (new_n > SIZE_MAX / sizeof *a) {
    /* reject the size */
} else {
    int *tmp = realloc(a, new_n * sizeof *a);
    if (tmp == NULL) {
        /* handle failure; a still owns its old block */
    } else {
        a = tmp;
    }
}

Als je het resultaat van realloc rechtstreeks toewijst aan de enige pointer die eigenaar is, verlies je bij een mislukking het adres van het oude blok. Handel nul afzonderlijk af: C17 maakt realloc met grootte nul door de implementatie bepaald, terwijl C23 dit ongedefinieerd maakt. Gebruik na een geslaagde groottewijziging de teruggegeven pointer en gebruik achterhaalde aliassen niet meer.

Zorg voor een duidelijk foutpad

Eén opruimsectie kan verkregen hulpbronnen in omgekeerde volgorde vrijgeven. Initialiseer pointers waarvan de code eigenaar is op NULL, sluit alleen geopende bestanden en geef een foutstatus terug nadat je de oorzaak hebt gemeld. Een goto cleanup kan deze paden gemakkelijker controleerbaar maken.

Vermijd system() voor taken met tekst van buitenaf: de shell kan die tekst als opdrachten interpreteren. Gebruik bij voorkeur een bibliotheekbewerking of een geschikte interface om een programma te starten waarbij argumenten afzonderlijk worden doorgegeven. Behandel opdrachtregelargumenten, omgevingswaarden, bestandsinhoud en netwerkgegevens als invoer die dezelfde zorg vereist als ingetypte tekst.

Belangrijkste punten

  • Controleer vóór gebruik van een getal of de conversie volledig is en de waarde binnen het toepassingsbereik valt.
  • Zet errno terug vóór strtol en bekijk de eindpointer.
  • Controleer bibliotheekfouten en behoud hulpbronnen op elk pad.
  • Gebruik een tijdelijke pointer voor realloc met een positieve grootte.
  • Handel een grootte van nul af en controleer op overloop bij vermenigvuldiging voordat je de grootte wijzigt.
  • Geef alleen hulpbronnen vrij die daadwerkelijk zijn verkregen.
  • Houd tekst van buitenaf uit strings met shellopdrachten.

5.4 Weeg keuzes rond geheugenveiligheid af

Geheugenveiligheid betekent dat geheugentoegang binnen de opslag blijft die een programma mag gebruiken. C geeft directe controle over het geheugen en laat controles van grenzen en levensduur aan de programmeur over.

Onderscheid grenzen van tijd

Ruimtelijke geheugenveiligheid houdt een toegang binnen de grenzen van het object. Element tien van een array met tien elementen schrijven schendt die veiligheid. Temporele geheugenveiligheid houdt toegang binnen de levensduur van het object. Een geheugenallocatie lezen na free, die tweemaal vrijgeven of een teruggegeven lokaal adres gebruiken schendt de regels voor levensduur.

De gevolgen kunnen verder gaan dan een crash. Een leesbewerking kan vertrouwelijke gegevens blootleggen. Een schrijfbewerking kan een bestand beschadigen, besturingsgegevens veranderen of een beveiligingskwetsbaarheid creëren wanneer invoer van buitenaf de bewerking stuurt. Een programma dat na beschadiging doorgaat, kan overtuigende maar onjuiste resultaten opleveren.

Deze last treft ook ervaren teams. Microsoft meldde in 2019 dat meer dan een decennium lang, jaar na jaar, ongeveer 70% van de kwetsbaarheden waaraan het bedrijf CVE-identifiers had toegekend over geheugenveiligheid ging. In december 2023 riepen CISA en partnerinstanties softwaremakers op om een overstap naar geheugenveilige programmeertalen te plannen. Dit zijn specifieke historische waarnemingen, geen gemeten percentage voor elk C-programma.

Begrijp waarom je C zou kiezen

C biedt controle over indeling, geheugenallocatie en toegang tot hardware, zonder een garbagecollector te vereisen. De kleine uitvoeringsomgeving past bij firmware met beperkte middelen. Er is brede compilerondersteuning, er zijn grote bestaande codebases en veel andere talen kunnen de interfaces aanroepen. Deze voordelen verklaren het gebruik in besturingssysteemkernen, ingebedde software, uitvoeringsomgevingen en bibliotheken.

Ze maken niet elk C-programma voorspelbaar snel: algoritmen, geheugenallocatie, caches en systeemaanroepen blijven van belang. Het relevante voordeel is controle over kosten en eisen aan de uitvoeringsomgeving.

AanpakGeheugenbeschermingKosten of verantwoordelijkheid
CGrenzen en levensduur afgedwongen door de programmeurHandmatig redeneren, controleren en testen
Veilig RustEigenaarschap, lenen en grenscontrolesDe eigenschapsregels leren
Go, Java, C#, PythonBeheerde levensduur en grenscontrolesExtra kosten voor uitvoeringsomgeving en geheugen verschillen
SwiftReferentietelling en gecontroleerde toegangKosten van eigenaarschap en uitvoeringsomgeving verschillen

Onveilige bewerkingen en interfaces met C vereisen bijzondere zorg in verder geheugenveilige systemen. Ook bij geheugenveiligheid moeten logische fouten en injectie via invoer nog worden aangepakt.

Kies lagen voor het resterende risico

Voorkom fouten met programmeerregels, waarschuwingen en codebeoordeling. Detecteer fouten met statische analyse, grenswaardetests, sanitizers en fuzzing. Beperk resterende schade met stapelbeveiliging, ondersteunde controles van _FORTIFY_SOURCE, randomisatie van de adresruimte-indeling (ASLR) en niet-uitvoerbaar geheugen. Een beschermingsmaatregel kan een aanval stoppen of beschadiging in een crash veranderen terwijl de fout in de broncode blijft bestaan.

Kosten bepalen waar je hulpmiddelen inzet. ASan verdubbelt doorgaans de uitvoeringstijd en verhoogt het geheugengebruik; Valgrind vertraagt de uitvoering doorgaans 20 tot 30 keer. Ze passen beter bij testen dan bij gewoon gebruik van productieversies. Beveiligingsversterking met lagere kosten blijft vaak in productieversies aanwezig.

Voor een nieuwe parser voor internetbestanden zonder eis die C vereist, vermindert een geheugenveilige programmeertaal een groot risico. Voor bestaande C-firmware op een microcontroller met 32 KB geheugen kunnen taalondersteuning en beperkte middelen pleiten voor C met strenge controles. Baseer de keuze op beperkingen, blootstelling aan invoer en onderhoudbaarheid; een risicovolle parser geleidelijk vervangen kan ook praktisch zijn.

Belangrijkste punten

  • Ruimtelijke veiligheid gaat over grenzen; temporele veiligheid gaat over levensduur.
  • Geheugenfouten kunnen gegevens blootleggen en resultaten beschadigen.
  • C biedt directe controle en brede compatibiliteit, met de last om veiligheid handmatig te waarborgen.
  • Geheugenveilige programmeertalen nemen veel geheugenfouten weg, maar andere soorten programmeerfouten blijven bestaan.
  • Combineer preventie, detectie en beschermingsmaatregelen in productie.
  • Kies een taal op basis van de werkelijke beperkingen van middelen en integratie.

Debuggen in C: voorbeelden

5 Walkthroughs

1

Bouw een programma dat je kunt onderzoeken

9 stappen
Lees feedback van de compiler, herstel de code die daarin wordt aangewezen en kies afzonderlijke bouwinstellingen voor debuggen en productie.
  1. 1
    IntroductieBegin vóór de eerste uitvoering

    Een programma kan compileren en toch een fout bevatten waarvoor de compiler kan waarschuwen. Je eerste taak is om die feedback als waarschuwingen op te vragen en te interpreteren.

    Je onderzoekt een onbedoelde toewijzing en een niet-passend formaat en kiest daarna instellingen voor een debuggersessie (een debugversie) en een productieversie. Deze keuzes bereiden latere hulpmiddelen voor om bruikbare aanwijzingen te tonen in plaats van onverklaarde adressen of ontbrekende lokale waarden.

  2. 2
    UitlegKies de waarschuwingsset

    Begin met -std=c17 -Wall -Wextra -Wpedantic. De eerste optie legt de taalversie vast; de andere schakelen algemene waarschuwingen, aanvullende waarschuwingen en diagnostische meldingen voor veel afwijkingen van die standaard in.

    -Wall betekent niet elke waarschuwing. Voeg -Wconversion toe voor impliciete conversies die waarden veranderen en -Wformat=2 voor strengere formaatcontroles. -Werror verandert ingeschakelde waarschuwingen in bouwfouten.

  3. 3
    VoorbeeldLees de voorwaarde

    De programmeur wil een lege verzameling melden:

    if (items = 0) {
        puts("No items");
    }
    

    Een compilerwaarschuwing wijst op een toewijzing in een voorwaarde. Vraag eerst wat de expressie doet: die slaat nul op in items, waarna de voorwaarde die nul test. De inhoud van de if-instructie wordt nooit uitgevoerd. Vervang = door == om het bestaande aantal te testen; haakjes rond de toewijzing toevoegen zou de verkeerde bewerking behouden.

  4. 4
    UitdagingHerstel het uitvoertype

    double ratio = 2.5; wordt afgedrukt met printf("%d\n", ratio);. Wat moet veranderen en waarom?

    ---reveal---

    Gebruik printf("%f\n", ratio);. %d verwacht een int, terwijl %f in printf een double verwacht. Een niet-passend formaat veroorzaakt ongedefinieerd gedrag. Een expliciete conversie naar int zou het afgedrukte getal veranderen door het gebroken deel weg te laten en zou dus niet voldoen aan het doel om de verhouding af te drukken.

  5. 5
    TipLees meldingen op volgorde

    Een diagnostische melding geeft meestal de broncodelocatie, het probleem en de verantwoordelijke optie. Lees ook de bijbehorende opmerkingen; die kunnen de conflicterende declaratie aanwijzen.

    Herstel de eerste compilatiefout en bouw daarna opnieuw. Een ontbrekende puntkomma kan de verdere analyse verstoren en een reeks meldingen veroorzaken. Onderzoek nadat de fouten zijn verdwenen de oorzaak van elke waarschuwing. Een bouwproces zonder waarschuwingen vereist nog steeds tests voor logische en invoerafhankelijke fouten.

  6. 6
    UitlegBehoud nuttige toestandsinformatie

    Voeg voor een eenvoudige debugversie -g -O0 toe. Debuginformatie koppelt de uitvoering aan namen en regels in de broncode; lage optimalisatie houdt meer tussentijdse toestandsinformatie beschikbaar voor onderzoek.

    -g schakelt optimalisatie niet uit. Met -O2 kan code nog steeds worden gedebugd, maar regels kunnen lijken te verspringen en variabelen kunnen worden weggeoptimaliseerd. -Og is een andere keuze voor ontwikkeling die lichte optimalisatie behoudt.

  7. 7
    VerbandKies controles voor verschillende taken

    Statische analyse volgt mogelijke paden in de broncode zonder het programma uit te voeren. Een sanitizer controleert bewerkingen op paden die de tests daadwerkelijk uitvoeren. Gebruik beide om de dekking uit te breiden.

    Beveiligingsversterking voor productie dient een ander doel: stapelbeveiliging en ondersteunde controles voor beveiligingsversterking kunnen sommige resterende beschadigingen detecteren of beperken. Die controles vereisen optimalisatie en geschikte bibliotheekondersteuning. Een gedetecteerde overschrijving moet nog steeds in de broncode worden hersteld.

  8. 8
    UitdagingBehoud validatie in productie

    Een debugversie gebruikt strenge waarschuwingen en asserties. Een productieversie voegt -O2 -DNDEBUG toe. De programmeur stelt voor assert(valid_input) als enige invoercontrole te gebruiken. Wat moet vóór productie veranderen?

    ---reveal---

    Valideer invoer met gewone besturingsstructuren en handel fouten af. NDEBUG verwijdert de evaluatie van asserties, zodat de voorgestelde controle verdwijnt. Houd waarschuwingen ingeschakeld en test zowel de productieconfiguratie als de debugversie; verschillende instellingen kunnen verborgen fouten zichtbaar maken.

  9. 9
    SamenvattingBreng de bouwinstellingen samen

    Gebruik compilermeldingen om bewerkingen te vinden die herstel nodig hebben. Stem formaten af op typen, onderzoek conversies en herstel vroege fouten voordat je de meldingen onderzoekt die daaruit voortvloeien.

    Bouw voor de volgende debuggersessie het juiste uitvoerbare bestand met broncode-informatie en een geschikt optimalisatieniveau. Houd configuraties herhaalbaar, voeg analyse en tests met sanitizers toe en behoud invoercontroles wanneer productie-instellingen asserties verwijderen.

2

Volg de oorzaak van een verkeerd totaal en een crash

12 stappen
Ga van reproductie naar stapsgewijze uitvoering, aanroepoverzichten en gerichte stops die de oorsprong van een verkeerde waarde aanwijzen.

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

  1. 1
    IntroductieMaak van een verschijnsel een vraag
  2. 2
    UitlegBewaar het falende voorbeeld
  3. 3
    UitdagingKies een gecontroleerd experiment
  4. 4
    VoorbeeldPauzeer bij de lusinhoud
  5. 5
    VoorbeeldVolg de geldige iteraties
  6. 6
    UitdagingHerstel en controleer de extra iteratie
  7. 7
    TipStap alleen nuttige code in
  8. 8
    VoorbeeldGa naar de aanroepende functie
  9. 9
    UitlegVang een wijziging op wanneer die plaatsvindt
  10. 10
    UitdagingOnderzoek een crash in de allocator
  11. 11
    VerbandGebruik een opgeslagen crashtoestand
  12. 12
    SamenvattingKies de weergave die de vraag beantwoordt
3

Controleer de bewerking voordat die ongeldig wordt

12 stappen
Redeneer over optimalisatie, rekenkundige grenzen en de levensduur van objecten zonder te vertrouwen op toevallige uitvoer.

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

  1. 1
    IntroductieVraag wat C garandeert
  2. 2
    UitlegOnderscheid variatie van een fout
  3. 3
    VoorbeeldEen overlooptest die te laat komt
  4. 4
    UitdagingPlaats de pointercontrole
  5. 5
    VoorbeeldBeveilig een som met teken
  6. 6
    KernpuntGedefinieerde terugloop kan het programma toch verstoren
  7. 7
    UitdagingVerbreed het type vóór het vermenigvuldigen
  8. 8
    TipControleer verschillende grenzen in expressies
  9. 9
    VoorbeeldOnderscheid een eindmarkering van een element
  10. 10
    VoorbeeldVolg elke alias bij vrijgave
  11. 11
    UitdagingHerstel een totaal en de levensduur ervan
  12. 12
    SamenvattingControleer type, bereik, grenzen en levensduur
4

Lees controles als aanwijzingen voor een specifieke fout

11 stappen
Kies controles met asserties, ASan en UBSan en verbind elk rapport vervolgens aan een blijvende oplossing.

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

  1. 1
    IntroductieControleer meer dan de uiteindelijke uitvoer
  2. 2
    UitlegControleer een interne belofte met een assertie
  3. 3
    UitdagingBehoud de bewerking in elke versie
  4. 4
    UitlegBouw een geïnstrumenteerd uitvoerbaar bestand
  5. 5
    VoorbeeldLees het gemelde gebied
  6. 6
    UitdagingHerstel de bijbehorende lus
  7. 7
    KernpuntEen ontbrekend rapport heeft meerdere verklaringen
  8. 8
    VoorbeeldLaat een rekenfout de test stoppen
  9. 9
    UitlegKies andere controlehulpmiddelen bewust
  10. 10
    UitdagingVerbeter een onvolledige controleroutine
  11. 11
    SamenvattingMaak van rapporten herhaalbare controles
5

Maak invoer, grootten en eigenaarschap expliciet

15 stappen
Pas programmeerregels toe op een klein invoerverwerkingspad en weeg daarna de veiligheidskosten van de keuze voor C af.

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

  1. 1
    IntroductieVolg gegevens door het programma
  2. 2
    UitlegGebruik de regel als vraag
  3. 3
    UitdagingHoud invoer als gegevens
  4. 4
    VoorbeeldGeef de capaciteit door vanuit de aanroepende functie
  5. 5
    VoorbeeldOnderscheid een veilig begin van succes
  6. 6
    UitdagingHandel een onvolledige regel af
  7. 7
    VoorbeeldBekijk het einde van de conversie
  8. 8
    VoorbeeldPas het bereik van het programma toe
  9. 9
    VoorbeeldBehoud de eigenaar bij een groottewijziging
  10. 10
    UitdagingPlan het foutpad
  11. 11
    UitlegBepaal de veiligheidsgrens
  12. 12
    VerbandKies lagen met verschillende taken
  13. 13
    UitdagingKies een programmeertaal
  14. 14
    UitdagingBeoordeel het volledige invoerpad
  15. 15
    SamenvattingHoud elke grens zichtbaar

Debuggen in C: oefenvragen

50 Oefenvragen in 5 sets

Set 1: Aanwijzingen van de compiler en keuzes in de debugger10 Oefenvragen

Vraag 1 van 10

Een bouwproces zonder meldingen met -Wall converteert een aantal van double stilzwijgend naar int. Welke extra waarschuwingsoptie richt zich op deze impliciete waardeverandering?
  1. -Wshadow
  2. -Wformat=2
  3. -Wconversion
  4. -Wpedantic
Toon antwoordVerberg antwoord
Antwoord: C
-Wconversion richt zich op impliciete conversies die een waarde kunnen veranderen, waaronder het weglaten van een gebroken deel van een drijvendekommagetal. -Wshadow gaat over een verborgen declaratie met dezelfde naam, niet over een typeverandering. Een waarschuwing moet ertoe leiden dat je de bedoelde waarde controleert voordat je een expliciete typeconversie toevoegt.

Vraag 2 van 10

Een programma moet een meetwaarde met een gebroken deel afdrukken. Het declareert double reading = 7.25; en roept printf("%d\n", reading); aan. Welke oplossing behoudt dat doel?
  1. Gebruik %f met reading.
  2. Converteer reading naar int.
  3. Gebruik %ld met reading.
  4. Converteer reading naar long.
Toon antwoordVerberg antwoord
Antwoord: A
printf verwacht een double voor %f, dus het formaat veranderen behoudt het gebroken deel en past bij het argument. Een expliciete conversie naar int zou bij %d kunnen passen, maar zou het gebroken deel weglaten dat het programma moet afdrukken. Het oorspronkelijke verschil veroorzaakt ongedefinieerd gedrag; het formaat is geen automatische numerieke conversie.

Vraag 3 van 10

Een debugger toont functieadressen maar kan geen broncoderegels of namen van lokale variabelen weergeven. Welke compileroptie levert de ontbrekende broncode-informatie?
  1. -Wextra
  2. -DNDEBUG
  3. -Werror
  4. -g
Toon antwoordVerberg antwoord
Antwoord: D
-g voegt de debuginformatie toe die de uitvoering koppelt aan broncodelocaties, typen en namen. -Wextra schakelt aanvullende diagnostische meldingen tijdens het compileren in, maar levert die koppelingen niet. Optimalisatie blijft een afzonderlijke keuze en kan nog steeds waarden verwijderen, ook als debuginformatie aanwezig is.

Vraag 4 van 10

Invoer met 240 records veroorzaakt een crash, maar elke helft slaagt wanneer die afzonderlijk wordt uitgevoerd. Wat is de nuttigste volgende strategie om het voorbeeld te verkleinen?
  1. Behoud alleen het laatste record en test het als afzonderlijke aanleiding.
  2. Verwijder kleinere stukken en behoud de reeks die het falen veroorzaakt.
  3. Verdubbel elke buffer en gooi de oorspronkelijke invoer weg.
  4. Voer een van de geslaagde helften uit totdat de crash terugkeert.
Toon antwoordVerberg antwoord
Antwoord: B
Het falen kan afhangen van meerdere records samen of van een opgebouwde toestand, dus behoud een reeks die faalt terwijl je kleinere stukken verwijdert. Eén record kiezen alleen omdat het het laatste is, veronderstelt onafhankelijkheid die de twee geslaagde helften niet aantonen. Elke verkleining moet worden getoetst aan het daadwerkelijke falen.

Vraag 5 van 10

De uitvoering is gepauzeerd bij een aanroep die je als één broncodestap wilt behandelen. Welke debuggeropdracht voert die aanroep normaal gesproken in één stap uit?
  1. step
  2. next
  3. print
  4. backtrace
Toon antwoordVerberg antwoord
Antwoord: B
next voert normaal gesproken de huidige broncoderegel uit en behandelt functieaanroepen daarbij als één stap. step is bedoeld om een aangeroepen functie in te gaan wanneer bruikbare broncode-informatie bestaat. Een breekpunt binnen de aanroep kan next nog steeds onderbreken, dus de opdracht beschrijft het stapgedrag en schakelt niet alle stops uit.

Vraag 6 van 10

Een breekpunt staat op result = compute();. Als je result afdrukt, zie je de eerdere waarde en bij deze stop is nog geen aanroep uitgevoerd. Hoe bekijk je het berekende resultaat?
  1. Voer print result opnieuw in zonder verder te gaan.
  2. Verwijder het breekpunt en bewerk de initialisator.
  3. Ga naar de aanroepende functie zonder de aanroep uit te voeren.
  4. Gebruik next en bekijk daarna de nieuwe waarde van result.
Toon antwoordVerberg antwoord
Antwoord: D
De stop gaat vooraf aan de toewijzing, dus result bevat nog de eerdere waarde. Door de regel met next uit te voeren, kunnen de aanroep en toewijzing worden voltooid voordat je de waarde bekijkt. Opnieuw afdrukken zonder verder te gaan toont dezelfde toestand en bewijst niet dat de toewijzing is mislukt.

Vraag 7 van 10

Een aanroepoverzicht toont stackframe nul in strlen en stackframe twee in je functie render. Je wilt de pointer caption van render in GDB bekijken. Wat doe je?
  1. Voer frame 2 uit en daarna p caption.
  2. Voer frame 0 uit en daarna p caption.
  3. Voer next uit en daarna p caption.
  4. Voer continue uit en daarna p caption.
Toon antwoordVerberg antwoord
Antwoord: A
Door een stackframe te selecteren, maak je de lokale context van de gekozen functie beschikbaar zonder meer code uit te voeren. frame 2 selecteert render, waar je caption kunt bekijken. In stackframe nul blijf je de bibliotheekcontext onderzoeken en de uitvoering hervatten is niet nodig als de aanwijzing die je nodig hebt al in een stackframe van een aanroepende functie staat.

Vraag 8 van 10

Een aantal wordt ergens tussen een bekende juiste en een bekende onjuiste waarneming verkeerd, maar de locatie van de schrijfbewerking is onbekend. Welke debuggerfunctie vindt die schrijfbewerking het best?
  1. Een breekpunt bij de laatste aanroep van printf.
  2. Een aanroepoverzicht na normale beëindiging.
  3. Een gegevensbreekpunt op de actieve telvariabele.
  4. Een broncodeoverzicht rond de declaratie.
Toon antwoordVerberg antwoord
Antwoord: C
Een gegevensbreekpunt stopt wanneer de bewaakte opslag verandert en laat de verantwoordelijke bewerking en de oude en nieuwe waarde zien. Een breekpunt bij de laatste afdruk toont alleen de toestand die al verkeerd is. Controleer elke schrijfbewerking waarbij het programma stopt aan de hand van de bedoelde regel voor het aantal, omdat sommige wijzigingen van die variabele terecht kunnen zijn.

Vraag 9 van 10

Een programma zonder waarschuwingen levert verschillende totalen op met -O0 en -O2. Met -O2 is ook een tussenvariabele niet beschikbaar in de debugger. Welk onderzoek behandelt beide waarnemingen?
  1. Verwijder optimalisatie blijvend en accepteer het oorspronkelijke debugtotaal.
  2. Behoud -O2 en behandel de ontbrekende variabele als beschadiging.
  3. Schakel debugsymbolen uit en vergelijk de afgedrukte adressen.
  4. Onderzoek ongedefinieerd gedrag met sanitizers en gebruik -g -O0 om te stappen.
Toon antwoordVerberg antwoord
Antwoord: D
Optimalisatie kan tussenwaarden op geldige wijze verwijderen, terwijl veranderende totalen een reden zijn om ongedefinieerde bewerkingen in de code te onderzoeken. Een versie met sanitizers helpt die bewerkingen te vinden en een debugversie met weinig optimalisatie helpt bij het onderzoek. Alleen het debugresultaat accepteren laat de oorzaak van het verschil met de productieversie onopgelost.

Vraag 10 van 10

Een parser crasht in free na het lezen van veel records. Een naambuffer vergroten stopt de zichtbare crash, maar bij de opgeslagen invoer meldt ASan nog steeds een schrijfbewerking voorbij die buffer. Welke handeling pakt de oorzaak aan?
  1. Behoud de grotere buffer en schakel ASan voor die test uit.
  2. Spoor de schrijfgrens op, herstel die en bewaar de invoer.
  3. Vervang de allocatoraanroep door een assertie en behoud de grotere buffer.
  4. Vang de crash in de allocator af en ga door met de records.
Toon antwoordVerberg antwoord
Antwoord: B
Het ASan-rapport wijst een ongeldige schrijfbewerking aan die ondanks het veranderde verschijnsel blijft bestaan. Herstel de index of kopieergrens, voer de oorspronkelijke invoer opnieuw uit en bewaar die als regressietest. Nabijgelegen opslag groter maken kan beschadiging voor een latere controle door de allocator verbergen zonder de oorspronkelijke toegang geldig te maken.
Set 2: Typen, bereiken en bestaande objecten10 Oefenvragen

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

Vraag 1 van 10

In merge(left(), right()) worden twee onafhankelijke aanroepen als argumenten doorgegeven. Beide evaluatievolgorden zijn toegestaan en er ontstaat geen conflict door gedeelde toestand. Hoe wordt die volgorde in C geclassificeerd?
  1. Ongedefinieerd gedrag.
  2. Niet-gespecificeerd gedrag.
  3. Door de implementatie bepaald gedrag.
  4. Een verplichte volgorde van links naar rechts.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Ga uit van een 32-bits unsigned int. Welke waarde slaat unsigned int slots = 0u - 1u; op?
  1. Nul, omdat waarden zonder teken niet negatief kunnen zijn.
  2. Min één, met behoud van het wiskundige resultaat.
  3. Geen gedefinieerde waarde, omdat de aftrekking onderloopt.
  4. 4.294.967.295, door gedefinieerde modulaire terugloop.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Ga uit van een 32-bits unsigned int. Invoer levert shift = 32 op. Wat is de status van 1u << shift?
  1. Ongedefinieerd: het aantal posities bereikt de bitbreedte van het type.
  2. Gedefinieerd als nul: alle ingestelde bits vallen buiten de resultaatbreedte.
  3. Gedefinieerd als één: het aantal loopt modulair terug volgens de bitbreedte.
  4. Door de implementatie bepaald: verschuivingen zonder teken verschillen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een array heeft zeven elementen. Wat is de grootste geldige elementindex?
  1. 7
  2. 8
  3. 6
  4. 5
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een functie geeft local terug nadat zij char local[24] = "ready"; heeft gedeclareerd. Waarom is het ongeldig als de aanroepende functie het resultaat leest?
  1. De array heeft te veel ongebruikte ruimte voor een string.
  2. De retourinstructie zet de array om in een stringliteral.
  3. De levensduur van de lokale array eindigde bij terugkeer van de functie.
  4. De aanroepende functie moet vóór elke stringleesbewerking sizeof toepassen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Welke voorwaarde detecteert voor int a met teken en een positieve int b overloop van a + b zonder die som te evalueren?
  1. a > INT_MAX - b
  2. a + b > INT_MAX
  3. a > INT_MAX + b
  4. a - b > INT_MAX
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een hulpfunctie voert int old = *p; uit vóór if (p == NULL) return -1;. Een test met een nulpointer als invoer lijkt bij lage optimalisatie de foutretour te bereiken, maar crasht in de productieversie. Welke oplossing zorgt voor een gedefinieerd pad bij een nulpointer als invoer?
  1. Behoud de oorspronkelijke volgorde en maak de ingelezen lokale variabele volatile.
  2. Behoud de volgorde en compileer zonder -Werror.
  3. Druk p af tussen de leesbewerking en de controle.
  4. Verplaats de controle op nul naar vóór de eerste dereferentie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Op een platform met een 32-bits int en een 64-bits long long zijn beide factoren van het type int: units = 70000, rate = 40000. Welke expressie berekent hun product correct in long long?
  1. (long long)(units * rate)
  2. (long long)units * rate
  3. (int)((long long)units * rate)
  4. (long long)(int)(units * rate)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een C-functie is via p eigenaar van een heapbuffer; alias wijst naar een plaats daarin. Een realloc met positieve grootte slaagt en het blok verplaatst zich. De functie wijst het resultaat toe aan p, schrijft via alias en geeft daarna zowel p als alias vrij. Welke correctie van het eigenaarschap is nodig?
  1. Gebruik de oude alias niet meer, gebruik de teruggegeven geheugenallocatie en geef die eenmaal vrij.
  2. Behoud de oude alias, maak het nieuwe blok groter en geef beide pointers vrij.
  3. Controleer de alias op nul, schrijf erdoorheen en geef alleen die pointer vrij.
  4. Gebruik de teruggegeven geheugenallocatie en geef de alias vóór de eigenaar vrij.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een controleversie gebruikt alleen -fsanitize=undefined. Een size_t-vermenigvuldiging loopt modulair terug maar levert geen melding op. Waarom kan dit gebeuren?
  1. De controle draait alleen wanneer NDEBUG is gedefinieerd.
  2. De terugloop blijft verborgen omdat size_t een pointer is.
  3. Terugloop zonder teken is gedefinieerd en valt buiten die groep.
  4. UBSan controleert alleen schrijfbewerkingen in arrays op stapel en heap.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 3: Asserties en sanitizerrapporten10 Oefenvragen

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

Vraag 1 van 10

Een interne hulpfunctie voor een lijst belooft dat het opgeslagen aantal na elke wijziging altijd gelijk is aan het aantal knopen. Wat voor programmeervoorwaarde is dit?
  1. Een invariant.
  2. Een invoerformaat.
  3. Een allocatieresultaat.
  4. Een compileroptie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een toepassing gebruikt assert(user_count > 0); als enige afwijzing van ongeldige aantallen van gebruikers. Productieversies definiëren NDEBUG. Wat moet die bescherming vervangen?
  1. Een statische assertie op dezelfde variabele tijdens de uitvoering.
  2. Een extra assertie nadat de geheugenallocatie slaagt.
  3. Een expliciete conversie van user_count naar een type zonder teken.
  4. Een gewone bereikcontrole met een foutpad.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

ASan meldt een schrijfbewerking van vier bytes “0 bytes after 28-byte region”. Ga ervan uit dat elk arrayelement vier bytes groot is. Welke gebruikte index begint op dat adres?
  1. Index 6.
  2. Index 7.
  3. Index 8.
  4. Index 28.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een test moet gewoon gebruik van niet-geïnitialiseerde waarden vinden. Waarom is een versie met alleen ASan onvoldoende?
  1. ASan volgt dat gebruik alleen op de heap.
  2. ASan volgt dat gebruik alleen bij globale variabelen.
  3. ASan volgt dat gebruik van waarden niet.
  4. ASan heeft de herstelmodus nodig om dat gebruik te volgen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een geautomatiseerde UBSan-test registreert overloop bij getallen met teken, maar gaat door en meldt succes. Welke optie laat een gedetecteerde fout door ongedefinieerd gedrag de uitvoering stoppen?
  1. -fno-omit-frame-pointer: behoud stackframes.
  2. -Wconversion: meld impliciete waardeveranderingen.
  3. -fno-sanitize-recover=undefined
  4. -D_FORTIFY_SOURCE=2: controleer ondersteunde kopieën.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een test schrijft voorbij een arrayveld van een struct naar het volgende veld en laat ook een product zonder teken voor het aantal bytes modulair teruglopen. ASan en -fsanitize=undefined blijven beide stil, maar de naastgelegen waarde en allocatiegrootte zijn verkeerd. Welk onderzoek pakt beide fouten aan?
  1. Onderzoek de veldgrenzen en controleer het grootteproduct zonder teken vóór gebruik.
  2. Accepteer de veldgrenzen en onderzoek alleen berekeningen met teken in het totaal.
  3. Vergroot de struct en accepteer de teruggelopen allocatiegrootte als gecontroleerd.
  4. Behoud de veldindex en verander elk opgeslagen aantal in een assertie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een team wil zowel overschrijdingen van heapgrenzen als gebruik van niet-geïnitialiseerde waarden onderzoeken. De huidige ASan-versie koppelt bibliotheken van derden die normaal zijn gecompileerd. Welke controleopzet voldoet aan de eisen van beide hulpmiddelen?
  1. Combineer ASan- en MSan-instrumentatie, maar behoud alle bibliotheken van derden ongewijzigd.
  2. Gebruik UBSan in plaats van beide en behoud de bibliotheken ongewijzigd.
  3. Voer het uitvoerbare ASan-bestand voor elke test uit onder Valgrind.
  4. Behoud ASan en bouw MSan afzonderlijk met compatibele afhankelijkheden.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Je hebt een geheugendump van een crash die niet meer te reproduceren is. Wat kun je doen met het bijbehorende uitvoerbare bestand?
  1. Doorgaan vanaf de crash en de invoer veranderen.
  2. Stackframes en beschikbare opgeslagen variabelen bekijken.
  3. Eerdere schrijfbewerkingen uit het opgeslagen aanroepoverzicht opnieuw uitvoeren.
  4. De dump opnieuw compileren tot een hersteld uitvoerbaar bestand.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een hulpfunctie voert assert(++used < capacity); uit voordat die op index used opslaat. Tests slagen, maar met NDEBUG overschrijft de functie herhaaldelijk dezelfde plaats. Welke broncodewijziging behoudt de bedoelde toestandswijziging in beide versies?
  1. Verhoog afzonderlijk en controleer daarna de interne grens met een assertie.
  2. Behoud de incrementatie in assert en gebruik -O0.
  3. Vervang de assertie ongewijzigd door static_assert.
  4. Maak used een waarde zonder teken en behoud de assertie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Fuzzing vindt een crash wanneer een parser “-3” converteert, die waarde expliciet naar size_t converteert, een buffer reserveert waarvan de grootte van het aantal afhangt en die vult. De parser controleert noch of de conversie volledig is, noch het toepassingsbereik. Welke volgorde van herstel pakt het pad van invoer naar geheugen aan?
  1. Vergroot de geheugenallocatie, converteer eerst naar een ander type en accepteer elke conversie die niet nul oplevert.
  2. Controleer het vullen na uitvoering en bepaal daarna of de conversie is geslaagd.
  3. Valideer conversie en aantal, controleer het grootteproduct en controleer daarna de geheugenallocatie.
  4. Maak het vulantal een waarde zonder teken en behoud de bestaande conversie en geheugenallocatie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 4: Strings, numerieke invoer en eigenaarschap van hulpbronnen10 Oefenvragen

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

Vraag 1 van 10

Een programma moet een string met afsluitend nulteken uit een bestand afdrukken zonder daarin opgenomen formaatconversies te interpreteren. Welke aanroep doet dat?
  1. printf(label);
  2. printf("%s", label);
  3. printf("%d", label);
  4. printf("%p", label);
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een aanroepende functie geeft char line[48] door aan read_line(char line[]) op een systeem met pointers van acht bytes. Wat meet sizeof line binnen de functie?
  1. De array van 48 bytes van de aanroepende functie.
  2. De huidige stringlengte.
  3. De arraygrootte plus een afsluitend nulteken.
  4. De pointerparameter van acht bytes.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

snprintf wordt aangeroepen met een bestemmingscapaciteit van 11 en geeft 11 terug. Wat staat daarmee vast?
  1. De volledige uitvoer paste niet.
  2. Er zijn precies elf tekens opgeslagen.
  3. De uitvoer paste met het afsluitende nulteken.
  4. Een coderingsfout verhinderde uitvoer.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Een strikte parser voor decimale getallen zet errno terug en converteert “64kg” met strtol. De conversie geeft 64 terug en errno blijft nul. Wat moet tot afwijzing leiden?
  1. De teruggegeven numerieke waarde verschilt van nul.
  2. Het argument voor het grondtal is decimaal.
  3. De eindpointer wijst tekst aan het einde aan.
  4. Het resultaat past binnen het bereik van long.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een programma accepteert decimale aantallen van 1 tot en met 900. Het verwerkt “ -4rest” met strtol, controleert alleen errno, converteert expliciet naar size_t en vermenigvuldigt met de elementgrootte voor de geheugenallocatie. Welke controles moeten aan de allocatie voorafgaan?
  1. Converteer het begin, vermenigvuldig en wijs daarna een nulresultaat en mislukte allocatie af.
  2. Vereis volledige conversie en een geaccepteerd bereik en controleer daarna het product zonder teken.
  3. Accepteer het begin als errno nul is en dwing daarna alleen de buffercapaciteit af.
  4. Verwijder het minteken, accepteer het begin en gebruik daarna een assertie voor de grootte.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een array is alleen via a in eigendom. Nadat een positief ingevoerd aantal in new_n is gelezen, voert de code a = realloc(a, new_n * sizeof *a); a[new_n - 1] = 0; uit. Elk geldig aantal moet worden afgehandeld en een mislukte groottewijziging moet de oude array behouden. Welke volgorde is vereist?
  1. Controleer het product, wijzig de grootte via een tijdelijke pointer en schrijf alleen na succes.
  2. Bereken eerst het product, wijzig de grootte rechtstreeks via a en controleer de oude grootte.
  3. Wijzig de grootte via een tijdelijke pointer, negeer nul en schrijf via de oorspronkelijke a.
  4. Controleer alleen op nul, vervang a en valideer het product na de schrijfbewerking.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een regelbuffer van 12 bytes ontvangt 11 tekens van fgets, zonder opgeslagen regelafbrekingsteken. De toepassing vereist één volledig record per regel. Welke afhandeling voorkomt dat een mogelijke rest als nieuw record wordt behandeld?
  1. Verwijder het laatste opgeslagen teken en accepteer daarna het begin als volledige regel.
  2. Verwerk het begin en laat de volgende aanroep een record maken.
  3. Voeg een afsluitend nulteken toe en verklaar de regel volledig.
  4. Bepaal welke invoer overblijft en handhaaf het beleid voor regels.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een functie ontvangt een bestemmingspointer en capaciteit 7. Die moet een volledige invoer van negen tekens behouden of een fout melden. De functie gebruikt strncpy(dst, src, sizeof dst) en drukt later dst af met %s. Ga uit van pointers van acht bytes. Welk herontwerp voldoet aan de eis?
  1. Behoud sizeof dst, voeg na acht bytes een afsluitend nulteken toe en druk het begin af.
  2. Kopieer zes tekens, voeg een afsluitend nulteken toe en meld succes voor het begin.
  3. Gebruik de opgegeven capaciteit, controleer het resultaat van een begrensde kopie en wijs afkapping af.
  4. Gebruik de invoer als printf-formaat zodat geen controle van de bestemmingsgrootte nodig is.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een programma wordt nu als C23 gebouwd. Het leest een size_t count uit een bestand en voert direct a = realloc(a, count * sizeof *a); uit. Lege arrays zijn toegestaan en een mislukte aanvraag moet de oude array behouden. Welk herontwerp voldoet voor elk aantal aan deze eisen?
  1. Roep realloc aan voor elke grootte, wijs toe aan de eigenaar en controleer daarna het product op overloop.
  2. Handel nul afzonderlijk af, controleer het product en gebruik daarna een tijdelijke pointer.
  3. Converteer het product naar een int met teken en roep daarna realloc aan.
  4. Gebruik assert(new_n > 0) en vervang daarna rechtstreeks de eigenaar.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een nieuwe bestandsparser verwerkt internetgegevens en heeft geen beperking voor integratie, hardware of middelen die C vereist. Welke ontwerpkeuze vermindert het risico van handmatig grens- en levensduurbeheer het meest direct?
  1. Gebruik een geheugenveilige programmeertaal voor de parser.
  2. Gebruik C met alleen een grotere standaardbuffer.
  3. Gebruik C en schakel alle dynamische geheugenallocaties uit.
  4. Gebruik C met alleen adresrandomisatie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 5: Herstel samenwerkende fouten10 Oefenvragen

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

Vraag 1 van 10

Een pointer wordt gebruikt nadat het object ervan is vrijgegeven. Welk onderdeel van geheugenveiligheid wordt geschonden?
  1. Formaatstringveiligheid.
  2. Ruimtelijke veiligheid.
  3. Numerieke veiligheid.
  4. Temporele veiligheid.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Voor int i = 4; in C17 schrijft een programmeur printf("%d %d\n", i, ++i);. Welke oplossing maakt de bedoelde uitvoer “oude waarde, daarna nieuwe waarde” gedefinieerd?
  1. Sla i op in old, verhoog i en druk daarna old en i af.
  2. Wissel de volgorde van de twee argumenten en behoud de incrementatie in de aanroep.
  3. Zet haakjes rond ++i en behoud beide argumenten.
  4. Converteer beide argumenten expliciet naar unsigned binnen dezelfde aanroep.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een CERT-risicobeoordeling geeft ernst 3, waarschijnlijkheid 2 en herstelscore 2. Welke prioriteit en welk niveau volgen daaruit?
  1. Prioriteit 7, niveau L2.
  2. Prioriteit 9, niveau L2.
  3. Prioriteit 12, niveau L1.
  4. Prioriteit 18, niveau L1.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Tekst wordt binnen dezelfde array naar links verschoven en de bytebereiken van bron en bestemming overlappen. Welke functie biedt voor deze situatie gedefinieerd kopiëren?
  1. memcpy
  2. memmove
  3. strncpy
  4. snprintf
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een functie is eigenaar van word, berekent int bytes = strlen(word);, reserveert dat aantal bytes, kopieert de volledige string, geeft het nieuwe blok vrij en retourneert het adres ervan. Invoer kan te lang zijn voor int. Welk ontwerp pakt het pad voor grootte en levensduur aan?
  1. Behoud het int-aantal, voeg één byte toe en blijf vóór terugkeer vrijgeven.
  2. Gebruik size_t, reserveer het aantal bytes van de lengte en draag het resulterende blok over.
  3. Bewaar een gecontroleerde size_t-lengte plus afsluitend nulteken en draag een geldige, gecontroleerde geheugenallocatie over.
  4. Behoud het int-aantal en de geheugenallocatie, maar verwijder alleen de vroege vrijgave.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een parser verwijdert een regelafbrekingsteken uit een volledige regel, roept strtol aan en controleert alleen errno != ERANGE. Daarna converteert de parser het resultaat expliciet naar int voor een arrayindex. Door welke ontbrekende controles kan nog steeds ongeldige invoer worden toegelaten?
  1. Of de invoer een decimaalteken heeft en of daar een cijfer voor staat.
  2. Alleen of het resultaat gelijk is aan LONG_MAX.
  3. Alleen of de conversie het decimale grondtal gebruikte.
  4. Ontbrekende cijfers, tekst aan het einde en het geldige indexbereik.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een grootteafhankelijke lus wordt onder ASan en UBSan alleen met één element getest. De test slaagt. In productie faalt de lus met een volle buffer en uitgeschakelde asserties. Welk controleplan pakt deze verschillen aan?
  1. Test grensgevallen als invoer in controleversies en productieversies.
  2. Herhaal de bestaande controletest met één element en extra diagnostische uitvoer.
  3. Houd asserties ingeschakeld en sla het geval met de volle buffer over.
  4. Voer statische analyse uit en laat alle dynamische tests vervallen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een bestaande C-parser schakelt stapelbeveiliging en ASLR in. Invoer uit fuzzing veroorzaakt nog steeds een door ASan gemelde schrijfbewerking voorbij een heapblok. Welke reactie combineert preventie en schadebeperking correct?
  1. Verwijder de sanitizer omdat beveiligingsversterking de schrijfbewerking definieert.
  2. Herstel de heapgrens en behoud de bescherming in productie.
  3. Schakel ASLR uit om de ongeldige schrijfbewerking geldig te maken.
  4. Vergroot het heapblok totdat de crash wordt uitgesteld.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Na een crash in een stringfunctie heeft stackframe nul geen bruikbare lokale variabelen. Stackframe drie in je code geeft een pointer door die door een zoekactie is teruggegeven. Die pointer is nul en ontbrekende items zijn geldige invoer. Welk herstel en welke controle volgen?
  1. Voeg een assertie tijdens de uitvoering toe bij de bibliotheekaanroep en behoud de bestaande aanroepende functie.
  2. Vervang de zoekactie door een grotere gereserveerde array.
  3. Gebruik continue totdat de pointer niet meer nul is.
  4. Handel de ontbrekende zoekuitkomst vóór de aanroep af en test daarna die invoer.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een functie opent een uitvoerbestand en reserveert twee buffers. De tweede geheugenallocatie mislukt. De opruimcode sluit het bestand en keert terug, maar geeft de eerste buffer nooit vrij. Welke correctie behoudt zowel eigenaarschap als foutmelding?
  1. Meld de fout en sluit het bestand, maar behoud de eerste buffer zonder aanroepende functie.
  2. Geef de eerste buffer vrij en sluit het bestand, maar meld succes voor het onvolledige werk.
  3. Geef de verkregen buffer vrij, controleer het sluiten en meld dat de bewerking is mislukt.
  4. Geef het tweede nulresultaat vrij en sluit het bestand, maar laat de eerste allocatie bestaan.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Kom je niet uit je eigen opgave? Huiswerkhulp

Kernbegrippen

78 Kernbegrippen, elk met een definitie

A

Aanroepoverzicht
Een aanroepoverzicht is een rapport van een debugger over actieve stackframes, doorgaans genummerd vanaf nul voor het binnenste stackframe.
Na een crash helpt bt je het eerste stackframe van de broncode te vinden dat bij je eigen code hoort.
Aanroepstapel
De aanroepstapel is de keten van actieve functieaanroepen, vanaf de huidige functie naar buiten via de functies die deze functie hebben aangeroepen.
Een aanroep van een stringbibliotheek in stackframe nul kan de ongeldige pointer van main hebben ontvangen.
AddressSanitizer
AddressSanitizer is compilerinstrumentatie die tijdens de uitvoering veel ongeldige bewerkingen rond grenzen, levensduur en vrijgave detecteert.
Een rapport kan de ongeldige schrijfbewerking aanwijzen en de geheugenallocatie waarvan die schrijfbewerking de grenzen overschreed.
Afkapping
Afkapping laat het deel van tekst weg dat niet in een bestemming past, ook wanneer het opgeslagen begin correct is afgesloten.
Een niet-negatieve retourwaarde van snprintf die minstens zo groot is als de capaciteit geeft aan dat de volledige tekst niet paste.
Afsluitend nulteken
Een afsluitend nulteken is het nulteken dat het einde van een C-string markeert en opslagruimte na de zichtbare tekens vereist.
De string “ready” heeft zes bytes nodig zodat stringfuncties na de vijf letters een afsluitend nulteken kunnen vinden.
ASLR
Randomisatie van de adresruimte-indeling (ASLR) varieert geheugenlocaties tussen uitvoeringen om sommige aanvallen op achtergebleven geheugenfouten moeilijker te maken.
ASLR kan misbruik van een ongeldige toegang bemoeilijken terwijl de oorspronkelijke schrijfbewerking buiten de grenzen onhersteld blijft.
Assertie
Een assertie is een uitvoerbare controle van een programmeeraanname die bij een onware voorwaarde, als de assertie is ingeschakeld, een fout meldt en het programma afbreekt.
Een interne hulpfunctie voor gemiddelden kan met een assertie controleren of de gevalideerde aanroepende functie een aantal groter dan nul doorgeeft.

B

Bereikfout
Een bereikfout treedt op wanneer de wiskundige waarde van een conversie buiten het bereik valt dat het resultaattype kan weergeven.
Na het terugzetten van errno stelt een bereikfout van strtol ERANGE in, ook al wordt een grenswaarde teruggegeven.
Beveiligingsversterking
Beveiligingsversterking voegt bescherming toe die bepaalde beschadigingen moet detecteren of de schade moet beperken van fouten die in een uitgebracht programma achterblijven.
Een controle voor stapelbeveiliging kan een functie stoppen nadat een overschrijving van de controlewaarde is gedetecteerd.
Breekpunt
Een breekpunt is een gekozen uitvoeringslocatie waar een debugger het programma stopt, normaal gesproken voordat de aangegeven instructie wordt uitgevoerd.
Met een breekpunt vóór sum += a[i] kun je i bekijken zonder de arraytoegang te evalueren.
Buffercapaciteit
Een buffercapaciteit is de totale hoeveelheid beschikbare beschrijfbare opslag, inclusief eventuele ruimte die nodig is voor het afsluitende nulteken van een string.
Een array van tien bytes kan hoogstens negen tekens van één byte opslaan als C-string met afsluitend nulteken.

C

Code die niet aan de regel voldoet
Code die niet aan de regel voldoet is een voorbeeld dat onder de door die regel beschreven voorwaarden een bepaalde programmeerregel overtreedt.
Gebruik van printf(input) laat gegevens van buitenaf formaatconversies aanleveren en schendt FIO30-C.
Compilatiefout
Een compilatiefout is een diagnostische melding die een geslaagd bouwproces verhindert totdat het gemelde probleem is opgelost.
Door een ontbrekende puntkomma te herstellen, kunnen meerdere latere meldingen verdwijnen die door dezelfde syntaxisfout zijn veroorzaakt.
Compilerwaarschuwing
Een compilerwaarschuwing meldt verdachte code of een gedetecteerd probleem, terwijl het compileren normaal gesproken kan doorgaan.
Een toewijzing die als voorwaarde wordt gebruikt, kan succesvol compileren en toch een waarschuwing opleveren.

D

Datarace
Een datarace treedt op wanneer threads conflicterende toegangen uitvoeren zonder vereiste synchronisatie, waarbij minstens één toegang een schrijfbewerking is.
Twee niet-gesynchroniseerde wijzigingen van gedeelde toestand hoeven niet op hetzelfde moment plaats te vinden om een datarace te vormen.
Debugger
Een debugger bestuurt de uitvoering van een programma, zodat je het kunt pauzeren, de toestand kunt bekijken en geselecteerde bewerkingen kunt doorlopen.
GDB en LLDB kunnen vóór een lusinhoud stoppen zodat je de huidige index met de grens kunt vergelijken.
Debuginformatie
Debuginformatie beschrijft namen, typen en locaties uit de broncode in een uitvoerbaar bestand, zodat inspectiehulpmiddelen instructies aan code kunnen koppelen.
De optie -g helpt een debugger de broncoderegel en lokale variabelen bij een gewoon breekpunt te herkennen.
Debugversie
Een debugversie is een uitvoerbaar bestand dat voor inspectie is geconfigureerd, meestal met broncode-informatie en weinig optimalisatie.
Een versie met -g -O0 maakt een foutieve lus gemakkelijker te volgen per broncode-instructie.
Diagnostische compilermelding
Een diagnostische compilermelding is een melding die een broncodelocatie en een vermoedelijk of vastgesteld probleem vermeldt dat tijdens het compileren is gevonden.
Een diagnostische melding over een formaat kan naar %d wijzen en het niet-passende argument van het type double aanwijzen.
Door de implementatie bepaald gedrag
Door de implementatie bepaald gedrag staat een keuze toe die elke C-implementatie moet documenteren, zodat programma’s er rekening mee kunnen houden.
Een C17-implementatie documenteert of het gewone type char een teken heeft of niet.
Dubbele vrijgave
Een dubbele vrijgave probeert dezelfde geheugenallocatie meer dan eenmaal vrij te geven zonder dat een nieuwe geldige geheugenallocatie eigenaarschap heeft gevestigd.
Een opruimtak die een al vrijgegeven blok vrijgeeft, kan een melding van de allocator of ASan veroorzaken.

E

Eigenaarschap
Eigenaarschap is de verantwoordelijkheid van een programma voor het beheren van de levensduur van een hulpbron, waaronder het vrijgeven en behouden van de geldige verwijzing ernaar.
Een tijdelijk resultaat behoudt de oorspronkelijke eigenaar van de geheugenallocatie wanneer een realloc met positieve grootte mislukt.
Eindpointer
Een eindpointer die door strtol wordt teruggegeven, wijst het eerste niet-geconverteerde teken aan, of het begin van de invoer als geen cijfers zijn verwerkt.
Bij “73x” wijst de eindpointer de afsluitende “x” aan die een strikte parser voor getallen afwijst.
Expressie-evaluatie
Expressie-evaluatie door een debugger berekent een expressie met de huidige toestand van het geselecteerde stackframe terwijl de uitvoering is gepauzeerd.
Door count af te drukken bekijk je een scalaire waarde; het evalueren van *p probeert toegang via die pointer.

F

Falen
Falen is een waargenomen afwijking van het bedoelde gedrag, zoals een crash, een verkeerd resultaat of ontbrekende uitvoer.
Een crash in free kan het zichtbare falen zijn dat wordt veroorzaakt door een schrijfbewerking veel eerder in de uitvoering.
Formaatstring
Een formaatstring vertelt een functie voor opgemaakte invoer of uitvoer hoe argumenten moeten worden geïnterpreteerd, zodat de conversies bij hun werkelijke typen moeten passen.
Een formaat van printf gebruikt %zu voor size_t; gebruikerstekst hoort in een afzonderlijk gegevensargument.
Fuzzing
Fuzzing test een programma met veel automatisch gegenereerde of gewijzigde invoer om falende gevallen te ontdekken die geplande tests missen.
Een gewijzigd record kan een parsertak bereiken waar een sanitizer een ongeldige toegang detecteert.

G

Gebruik na vrijgave
Gebruik na vrijgave benadert een geheugenallocatie nadat die is vrijgegeven en schendt daarmee de levensduur van het oorspronkelijke object.
Als een pointer die eigenaar is wordt vrijgegeven, eindigt ook geldige toegang via een alias die het oude adres nog bevat.
Gegevensbreekpunt
Een gegevensbreekpunt stopt de uitvoering wanneer een bewaakte geheugenlocatie verandert en helpt de instructie te vinden die de waarde schreef.
Het bewaken van het aantal in een structuur kan lang vóór een latere crash een onverwachte toewijzing onthullen.
Geheugendump
Een geheugendump is een opgeslagen momentopname van een proces bij een crash die een debugger met het bijbehorende uitvoerbare bestand kan onderzoeken.
Een opgeslagen dump maakt een aanroepoverzicht en onderzoek van lokale waarden mogelijk nadat het falende proces is geëindigd.
Geheugenveilige programmeertaal
Een geheugenveilige programmeertaal dwingt voor veilige bewerkingen regels voor grenzen en levensduur af, waardoor minder handmatige geheugenveiligheidscontroles nodig zijn.
Veilig Rust combineert eigenschapsregels met grenscontroles; onveilige C-code aanroepen vereist nog steeds zorg.
Gelaagde beveiliging
Gelaagde beveiliging combineert preventie, detectie en schadebeperking, zodat meerdere beschermingslagen achtergebleven fouten aanpakken.
Waarschuwingen en codebeoordeling voorkomen programmeerfouten, sanitizers detecteren ze in tests en beveiligingsversterking beperkt bepaalde schade.

H

Hypothese
Een hypothese bij debuggen is een voorgestelde verklaring van falen die een voorspelling oplevert die je met een waarneming of wijziging kunt toetsen.
Als je een ontbrekend veld vermoedt, kun je de pointer ervan direct vóór de stringverwerking bekijken.

I

Impliciete conversie
Een impliciete conversie verandert het type van een waarde zonder geschreven expliciete typeconversie en kan informatie verliezen of de waarde veranderen.
Als je een double aan een int toewijst, valt het gebroken deel weg wanneer de afgekaptе waarde past.
Instrumentatie
Instrumentatie voegt controle- of observatiecode toe aan een programma, zodat hulpmiddelen bewerkingen tijdens de uitvoering kunnen onderzoeken.
Een ASan-versie heeft geïnstrumenteerde objectbestanden en de uitvoeringsomgeving van de sanitizer nodig bij de laatste koppelstap.
Integerpromotie
Een integerpromotie converteert een kleiner geheelgetaltype naar int of unsigned int vóór veel rekenkundige bewerkingen en bitverschuivingen.
De geldigheid van het aantal verschoven posities hangt af van de bitbreedte van de gepromoveerde linkeroperand, niet alleen van de oorspronkelijke schrijfwijze.
Invariant
Een invariant is een eigenschap die behouden blijft tijdens herhaalde stappen of geldige toestanden van een lus of gegevensstructuur.
Het opgeslagen aantal van een lijst moet na elke voltooide invoeging overeenkomen met het aantal knopen.

L

Levensduur van een object
De levensduur van een object is de periode tijdens de uitvoering waarin een object bestaat en de opslag volgens de regels van C mag worden benaderd.
Een lokale automatische array bestaat tijdens de functieaanroep, terwijl een geheugenallocatie tot de vrijgave blijft bestaan.

M

MemorySanitizer
MemorySanitizer is het hulpmiddel van Clang dat tijdens de uitvoering gebruik van niet-geïnitialiseerde waarden volgt en compatibele instrumentatie van afhankelijkheden vereist.
Het richt zich op een soort fouten met niet-geïnitialiseerde waarden die gewone ASan-controles niet volgen.
Minimaal reproducerend voorbeeld
Een minimaal reproducerend voorbeeld is een kleine combinatie van invoer en programma die nog steeds het onderzochte falen veroorzaakt.
Na het verwijderen van niet-gerelateerde records uit de invoer van een crashende parser blijft een leeg veld over dat de crash nog steeds consequent veroorzaakt.
Modulaire terugloop bij gehele getallen zonder teken
Modulaire terugloop bij gehele getallen zonder teken brengt een rekenkundig resultaat terug binnen het bereik van het type zonder teken; dit is gedefinieerd maar kan aannames van een programma over grootten schenden.
Een product voor het aantal bytes dat overloopt kan een veel kleinere geheugenallocatie aanvragen dan de bedoelde array nodig heeft.

N

Naamverberging bij variabelen
Naamverberging bij variabelen treedt op wanneer een declaratie in een binnenliggend geldigheidsgebied een andere declaratie met dezelfde naam verbergt.
De count van een binnenste lus kan worden bijgewerkt terwijl de buitenste count ongewijzigd blijft.
Niet-geordende neveneffecten
Niet-geordende neveneffecten zijn wijzigingen en conflicterend gebruik van dezelfde scalaire waarde zonder de ordening die C voor gedefinieerde evaluatie vereist.
Zowel i als ++i aan één aanroep doorgeven mengt een leesbewerking en een wijziging zonder veilige ordening.
Niet-gespecificeerd gedrag
Niet-gespecificeerd gedrag staat een keuze tussen geldige uitkomsten toe zonder dat de implementatie hoeft te documenteren welke zij kiest.
Onafhankelijke aanroepen die als argumenten worden gebruikt, mogen in beide volgorden worden geëvalueerd zonder de aanroep ongeldig te maken.
Niet-vertrouwde invoer
Niet-vertrouwde invoer is gegevens buiten de controle van het programma waarvan formaat, lengte en bereik vóór gevoelig gebruik moeten worden gecontroleerd.
Een aantal uit de opdrachtregel moet worden gevalideerd voordat het een arrayallocatie of lusgrens bepaalt.

O

Onbepaalde waarde
Een onbepaalde waarde is de toestand van een object waarvan de bruikbare waarde niet is vastgesteld, zoals bij veel niet-geïnitialiseerde automatische variabelen.
Als je int sum; declareert en direct sum += x; gebruikt, lees je een waarde die nooit is geïnitialiseerd.
Ongedefinieerd gedrag
Ongedefinieerd gedrag is gedrag waaraan de C-standaard geen eisen stelt nadat een ongeldige bewerking plaatsvindt.
Overloop bij getallen met teken kan in de ene versie modulair lijken terug te lopen en zich na optimalisatie anders gedragen.
Ongeldige pointer naar een niet meer bestaand object
Een ongeldige pointer naar een niet meer bestaand object verwijst naar opslag waarvan de levensduur van het object is geëindigd en kan dus geen geldige toegang tot dat oude object bieden.
Als een functie het adres van een lokale array teruggeeft, krijgt de aanroepende functie een pointer naar opslag waarvan de levensduur is geëindigd.
Oplossing die aan de regel voldoet
Een oplossing die aan de regel voldoet is een ontwerp of codevoorbeeld dat onder de vermelde voorwaarden van de oplossing aan de eis van een regel voldoet.
Een vast formaat %s houdt aangeleverde tekst in het gegevensargument van een printf-aanroep.
Opruimpad
Een opruimpad geeft daadwerkelijk verkregen hulpbronnen vrij voordat een functie terugkeert, ook na een gedeeltelijk voltooide bewerking.
Een gedeelde opruimsectie kan een buffer vrijgeven en een bestand sluiten nadat een latere geheugenallocatie mislukt.
Optimalisatie
Optimalisatie zet een programma om om de uitvoering of opslag te verbeteren, terwijl het vereiste gedrag van gedefinieerde code behouden blijft.
Met -O2 toont een debugger mogelijk geen afzonderlijke waarde voor een lokale variabele die de compiler heeft verwijderd.
Overdraagbaarheid
Overdraagbaarheid is het vermogen van code om zich bij verschillende implementaties te gedragen zoals bedoeld, zonder afhankelijk te zijn van niet-ondersteunde aannames over het platform.
Door INT_MAX te gebruiken, voorkom je de aanname dat elke implementatie int dezelfde maximumwaarde geeft.
Overlappende geheugenbereiken
Overlappende geheugenbereiken delen minstens één byte opslag, zodat kopiëren ertussen gedrag vereist dat de oorspronkelijke brongegevens behoudt.
Tekst binnen dezelfde array verschuiven kan memmove vereisen omdat memcpy overlap verbiedt.
Overloop bij gehele getallen met teken
Overloop bij gehele getallen met teken treedt op wanneer een rekenkundig resultaat niet in het doeltype met teken past en veroorzaakt ongedefinieerd gedrag.
Bij een 32-bits int kan één optellen bij INT_MAX geen overdraagbare, gedefinieerde volgende waarde opleveren.

P

Postconditie
Een postconditie is een eigenschap die een bewerking belooft tot stand te brengen wanneer die met succes wordt voltooid.
Na het sorteren van een array kan een postconditie stellen dat elk element niet groter is dan het volgende.
Preconditie
Een preconditie is een eis die moet gelden wanneer een functie of bewerking begint en die door de aanroepende functie of eerdere validatie wordt vastgesteld.
Een interne hulpfunctie kan een pointer vereisen die geen nulpointer is voordat zij het eerste arrayelement onderzoekt.
Productieversie
Een productieversie is een uitvoerbaar bestand dat voor gewoon gebruik is geconfigureerd, meestal met optimalisatie en ondersteunde beveiligingsversterking ingeschakeld.
Een productieversie moet invoercontroles behouden, ook wanneer NDEBUG de asserties verwijdert.
Programmeerfout
Een programmeerfout is een fout in code die onjuist gedrag kan veroorzaken wanneer een bepaald pad of bepaalde invoer die fout bereikt.
Een lusvoorwaarde kan verkeerd zijn, ook bij uitvoeringen waarin de ongeldige toegang die daaruit volgt geen zichtbare crash veroorzaakt.

Q

Quarantaine
Een quarantaine van ASan stelt hergebruik van vrijgegeven blokken uit, waardoor de kans groter wordt dat achterhaalde toegangen geheugen raken dat nog als ongeldig is gemarkeerd.
Een ongeldige pointer naar een niet meer bestaand object kan worden gedetecteerd voordat het vrijgegeven blok voor een andere geheugenallocatie wordt hergebruikt.

R

Regressietest
Een regressietest controleert gedrag dat eerder faalde, zodat bij latere wijzigingen kan worden nagegaan of die fout terugkeert.
Opgeslagen invoer met een leeg record die ooit een parser liet crashen, wordt na elke parserwijziging opnieuw uitgevoerd om een terugkeer van die crash te vinden.
Rekenen met overloopcontrole
Rekenen met overloopcontrole detecteert een wiskundig resultaat dat niet kan worden weergegeven zonder dat het programma ongedefinieerde overloop hoeft uit te voeren.
ckd_add van C23 meldt of een som past, zodat het programma een fout expliciet kan afhandelen.
Risicoprioriteit
Een CERT-risicoprioriteit is het product van scores voor ernst, waarschijnlijkheid en herstel, dat helpt bij het ordenen van handhavings- of herstelwerk.
Een hogere herstelscore verhoogt de prioriteit omdat de bijbehorende oplossing goedkoper is om uit te voeren.
Ruimtelijke geheugenveiligheid
Ruimtelijke geheugenveiligheid vereist dat elke geheugentoegang binnen de grenzen blijft van het object dat mag worden benaderd.
Een grenscontrole vóór een schrijfbewerking in een array voorkomt dat een index een element buiten die array selecteert.

S

Schaduwgeheugen
Schaduwgeheugen is hulpgeheugen dat een controlehulpmiddel gebruikt om informatie over de geheugenbytes van de toepassing vast te leggen.
ASan raadpleegt schaduwinformatie om te controleren of een geïnstrumenteerde toegang toegestane opslag raakt.
SEI CERT C Coding Standard
De SEI CERT C Coding Standard is de verzameling C-regels, aanbevelingen, voorbeelden en risicobeoordelingen van Carnegie Mellon SEI.
De regel MEM35-C helpt beoordelaars te vragen of een geheugenallocatie genoeg bytes voor de objecten bevat.
Shellinjectie
Shellinjectie treedt op wanneer tekst van buitenaf de structuur van een shellopdracht verandert en onbedoelde opdrachten of bewerkingen veroorzaakt.
Als je een system()-string opbouwt uit een ongecontroleerd argument, kan een shell de aangeleverde syntaxis interpreteren.
Stackframe
Een stackframe is de uitvoeringscontext van een actieve functieaanroep, met de beschikbare argumenten, lokale variabelen en retourinformatie.
Wanneer een aanroepende functie een pointer name doorgeeft, maakt het selecteren van het stackframe van die functie na een crash de pointer beschikbaar voor onderzoek.
Standaard voor veilig programmeren
Een standaard voor veilig programmeren is een verzameling toepasbare eisen en aanbevelingen om bekende fouten en kwetsbaarheden te voorkomen.
Een controlelijst voor codebeoordeling kan elke controle van een allocatiegrootte verbinden aan de fout die de controle moet voorkomen.
Stapsgewijze uitvoering
Stapsgewijze uitvoering laat de uitvoering met een gekozen stap op broncodeniveau verdergaan, zodat je tussen instructies of functieaanroepen kunt kijken.
Gebruik next om een aanroep als één stap uit te voeren en step wanneer je de eigen code van de functie moet ingaan.
Statische analyse
Statische analyse onderzoekt broncode en mogelijke uitvoeringspaden zonder het programma met testinvoer uit te voeren.
Een analysetool kan een pad met dubbele vrijgave vinden dat geen enkele huidige regressietest bereikt.
Statische assertie
Een statische assertie controleert een constante expressie tijdens het compileren en laat het bouwproces mislukken wanneer de voorwaarde onwaar is.
Een static_assert in C17 kan een platform afwijzen waarop een vereiste aanname over de grootte van int onwaar is.
Stringlengte
Een stringlengte is het aantal tekens vóór het eerste afsluitende nulteken, exclusief dat afsluitende teken zelf.
strlen("safe") is vier, terwijl opslag van de volledige string vijf bytes vereist.

T

Temporele geheugenveiligheid
Temporele geheugenveiligheid vereist dat toegang alleen plaatsvindt zolang een object bestaat en geldig is voor die toegang.
Een achterhaalde alias na free schendt de levensduurveiligheid, ook als het adres nog bekend oogt.
ThreadSanitizer
ThreadSanitizer is een controlehulpmiddel voor de uitvoering dat conflicterende niet-gesynchroniseerde toegangen tot gedeeld geheugen door threads aanwijst.
Het vereist een afzonderlijke versie naast ASan bij het onderzoeken van mogelijke dataraces.
Toegang buiten de grenzen
Toegang buiten de grenzen leest of schrijft buiten de geldige elementen van een object, ook als het adres toevallig toegankelijk is.
Index zes valt buiten een array met zes elementen omdat het laatste geldige element index vijf heeft.

U

UndefinedBehaviorSanitizer
UndefinedBehaviorSanitizer voegt controles toe voor veel ongedefinieerde bewerkingen tijdens de uitvoering, waaronder overloop bij getallen met teken en ongeldige bitverschuivingen.
Een UBSan-rapport kan een overlopende optelling aanwijzen, ook als het programma een aannemelijk totaal afdrukt.

V

Verboden geheugenzone
Een verboden geheugenzone is een verboden geheugengebied rond een ondersteund object, zodat een controlehulpmiddel toegang over de grens ervan kan detecteren.
Een extra schrijfbewerking direct na een heapblok kan de verboden geheugenzone binnengaan en ASan activeren.
Voorwaardelijk breekpunt
Een voorwaardelijk breekpunt stopt op een gekozen locatie alleen wanneer de voorwaarde in de huidige programmatoestand waar is.
Een voorwaarde zoals i == 650 voorkomt dat je eerdere lusiteraties handmatig moet doorlopen.

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