Debuggen, ongedefinieerd gedrag en veilig C programmeren
BeginnerLes 8 van 8 in Cursus Programmeren in C: pointers, geheugenbeheer en debuggen
Wat je leert
- 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 compilermeldingenCompileer 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 productieGebruik afzonderlijke debugversies en productieversies, weet wat -g en de optimalisatieniveaus veranderen en voeg waar nodig statische analyse en opties voor beveiligingsversterking toe.
- Schakel waarschuwingen in en lees compilermeldingen
- 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 hypotheseMaak 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 lldbStart 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 waardenGebruik 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.
- Debug systematisch: reproduceer het probleem, isoleer de oorzaak en toets een hypothese
- 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 betekentOnderscheid 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 expressiesHerken 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 geheugengebruikHerken 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.
- Begrijp wat ongedefinieerd gedrag betekent
- 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 assertiesGebruik 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 AddressSanitizerBouw 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 controleroutineGebruik UndefinedBehaviorSanitizer en leer de andere sanitizers kennen. Combineer vervolgens waarschuwingen, sanitizers en tests tot een routine die fouten vroeg opspoort.
- Leg aannames vast met asserties
- 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 programmerenLeg 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 buffersWeet 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 afBehandel 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 afLeg 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.
- Gebruik een standaard voor veilig programmeren
Debuggen in C: uitleg
1Laat 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:
| Optie | Wat die helpt te vinden |
|---|---|
-Wconversion | Impliciete conversies die een waarde kunnen veranderen |
-Wshadow | Een lokale declaratie die een andere variabele verbergt |
-Wformat=2 | Aanvullende controles van formaatstrings |
-Werror | Laat 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.
-Wallschakelt 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:
| Instelling | Gebruikelijk doel | Wat je kunt zien |
|---|---|---|
-O0 | Eenvoudig debuggen | De volgorde van de broncode is meestal het gemakkelijkst te volgen |
-Og | Debuggen met lichte optimalisatie | Sommige omzettingen blijven aanwezig |
-O2 | Efficiënte productieversie | Verplaatste 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
-gvoor debuginformatie op broncodeniveau. - Debug met
-O0of-Ogen test ook de productieversie. - Houd invoercontroles actief wanneer
NDEBUGasserties 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.
2Spoor 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
| Opdracht | Doel in GDB en LLDB |
|---|---|
next of n | Voer de huidige regel uit en behandel aanroepen normaal gesproken als één stap |
step of s | Ga een aangeroepen functie in wanneer de broncode-informatie dat toestaat |
finish | Voer uit totdat de geselecteerde functie terugkeert |
continue of c | Ga 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,finishencontinueom 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
btom 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.
3Herken 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
| Soort | Wat de taal toestaat | Voorbeeld in C17 |
|---|---|---|
| Ongedefinieerd | Geen vereist gedrag | Overloop bij gehele getallen met teken |
| Door de implementatie bepaald | Een gedocumenteerde keuze van de implementatie | Of een gewone char een teken heeft |
| Niet-gespecificeerd | Een toegestane keuze die niet hoeft te worden gedocumenteerd | De 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.
4Spoor 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:
| Soort | Wat het vastlegt | Voorbeeld |
|---|---|---|
| Preconditie | Vereist bij binnenkomst | Een niet-lege invoerarray |
| Postconditie | Gegarandeerd na de bewerking | Een array is gesorteerd |
| Invariant | Blijft gelden tussen stappen | Een 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.
NDEBUGschakelt 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:
- Bepaal de foutcategorie en of de toegang een lees- of schrijfbewerking was.
- Zoek in het toegangsoverzicht het eerste stackframe in je eigen code.
- Vergelijk de toegang met het gereserveerde gebied en de elementgrootte.
- 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=addressen 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
| Hulpmiddel | Hoofddoel | Belangrijke beperking |
|---|---|---|
| UBSan | Veel ongedefinieerde bewerkingen met berekeningen, bitverschuivingen, nul, uitlijning en arrays | Detecteert niet elke soort ongedefinieerd gedrag |
| ASan | Veel fouten bij grenzen en levensduur | Volgt gewone niet-geïnitialiseerde waarden niet |
| MemorySanitizer | Gebruik van niet-geïnitialiseerd geheugen | Heeft geïnstrumenteerde afhankelijkheden nodig voor betrouwbare resultaten |
| ThreadSanitizer | Dataraces | Vereist 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.
5Schrijf 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
snprintfop mislukking en afkapping. - Gebruik
memmovevoor 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
errnoterug vóórstrtolen bekijk de eindpointer. - Controleer bibliotheekfouten en behoud hulpbronnen op elk pad.
- Gebruik een tijdelijke pointer voor
reallocmet 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.
| Aanpak | Geheugenbescherming | Kosten of verantwoordelijkheid |
|---|---|---|
| C | Grenzen en levensduur afgedwongen door de programmeur | Handmatig redeneren, controleren en testen |
| Veilig Rust | Eigenaarschap, lenen en grenscontroles | De eigenschapsregels leren |
| Go, Java, C#, Python | Beheerde levensduur en grenscontroles | Extra kosten voor uitvoeringsomgeving en geheugen verschillen |
| Swift | Referentietelling en gecontroleerde toegang | Kosten 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
1Bouw een programma dat je kunt onderzoeken
9 stappen
- 1IntroductieBegin 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.
- 2UitlegKies 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.-Wallbetekent niet elke waarschuwing. Voeg-Wconversiontoe voor impliciete conversies die waarden veranderen en-Wformat=2voor strengere formaatcontroles.-Werrorverandert ingeschakelde waarschuwingen in bouwfouten. - 3VoorbeeldLees 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. - 4UitdagingHerstel het uitvoertype
double ratio = 2.5;wordt afgedrukt metprintf("%d\n", ratio);. Wat moet veranderen en waarom?---reveal---
Gebruik
printf("%f\n", ratio);.%dverwacht eenint, terwijl%finprintfeendoubleverwacht. Een niet-passend formaat veroorzaakt ongedefinieerd gedrag. Een expliciete conversie naarintzou 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. - 5TipLees 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.
- 6UitlegBehoud nuttige toestandsinformatie
Voeg voor een eenvoudige debugversie
-g -O0toe. Debuginformatie koppelt de uitvoering aan namen en regels in de broncode; lage optimalisatie houdt meer tussentijdse toestandsinformatie beschikbaar voor onderzoek.-gschakelt optimalisatie niet uit. Met-O2kan code nog steeds worden gedebugd, maar regels kunnen lijken te verspringen en variabelen kunnen worden weggeoptimaliseerd.-Ogis een andere keuze voor ontwikkeling die lichte optimalisatie behoudt. - 7VerbandKies 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.
- 8UitdagingBehoud validatie in productie
Een debugversie gebruikt strenge waarschuwingen en asserties. Een productieversie voegt
-O2 -DNDEBUGtoe. De programmeur stelt voorassert(valid_input)als enige invoercontrole te gebruiken. Wat moet vóór productie veranderen?---reveal---
Valideer invoer met gewone besturingsstructuren en handel fouten af.
NDEBUGverwijdert 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. - 9SamenvattingBreng 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.
2Volg de oorzaak van een verkeerd totaal en een crash
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieMaak van een verschijnsel een vraag
- 2UitlegBewaar het falende voorbeeld
- 3UitdagingKies een gecontroleerd experiment
- 4VoorbeeldPauzeer bij de lusinhoud
- 5VoorbeeldVolg de geldige iteraties
- 6UitdagingHerstel en controleer de extra iteratie
- 7TipStap alleen nuttige code in
- 8VoorbeeldGa naar de aanroepende functie
- 9UitlegVang een wijziging op wanneer die plaatsvindt
- 10UitdagingOnderzoek een crash in de allocator
- 11VerbandGebruik een opgeslagen crashtoestand
- 12SamenvattingKies de weergave die de vraag beantwoordt
3Controleer de bewerking voordat die ongeldig wordt
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVraag wat C garandeert
- 2UitlegOnderscheid variatie van een fout
- 3VoorbeeldEen overlooptest die te laat komt
- 4UitdagingPlaats de pointercontrole
- 5VoorbeeldBeveilig een som met teken
- 6KernpuntGedefinieerde terugloop kan het programma toch verstoren
- 7UitdagingVerbreed het type vóór het vermenigvuldigen
- 8TipControleer verschillende grenzen in expressies
- 9VoorbeeldOnderscheid een eindmarkering van een element
- 10VoorbeeldVolg elke alias bij vrijgave
- 11UitdagingHerstel een totaal en de levensduur ervan
- 12SamenvattingControleer type, bereik, grenzen en levensduur
4Lees controles als aanwijzingen voor een specifieke fout
11 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieControleer meer dan de uiteindelijke uitvoer
- 2UitlegControleer een interne belofte met een assertie
- 3UitdagingBehoud de bewerking in elke versie
- 4UitlegBouw een geïnstrumenteerd uitvoerbaar bestand
- 5VoorbeeldLees het gemelde gebied
- 6UitdagingHerstel de bijbehorende lus
- 7KernpuntEen ontbrekend rapport heeft meerdere verklaringen
- 8VoorbeeldLaat een rekenfout de test stoppen
- 9UitlegKies andere controlehulpmiddelen bewust
- 10UitdagingVerbeter een onvolledige controleroutine
- 11SamenvattingMaak van rapporten herhaalbare controles
5Maak invoer, grootten en eigenaarschap expliciet
15 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVolg gegevens door het programma
- 2UitlegGebruik de regel als vraag
- 3UitdagingHoud invoer als gegevens
- 4VoorbeeldGeef de capaciteit door vanuit de aanroepende functie
- 5VoorbeeldOnderscheid een veilig begin van succes
- 6UitdagingHandel een onvolledige regel af
- 7VoorbeeldBekijk het einde van de conversie
- 8VoorbeeldPas het bereik van het programma toe
- 9VoorbeeldBehoud de eigenaar bij een groottewijziging
- 10UitdagingPlan het foutpad
- 11UitlegBepaal de veiligheidsgrens
- 12VerbandKies lagen met verschillende taken
- 13UitdagingKies een programmeertaal
- 14UitdagingBeoordeel het volledige invoerpad
- 15SamenvattingHoud elke grens zichtbaar
Debuggen in C: oefenvragen
Set 1: Aanwijzingen van de compiler en keuzes in de debugger10 Oefenvragen
Vraag 1 van 10
-Wall converteert een aantal van double stilzwijgend naar int. Welke extra waarschuwingsoptie richt zich op deze impliciete waardeverandering?Toon antwoordVerberg antwoord
-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
double reading = 7.25; en roept printf("%d\n", reading); aan. Welke oplossing behoudt dat doel?Toon antwoordVerberg antwoord
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
Toon antwoordVerberg antwoord
-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
Toon antwoordVerberg antwoord
Vraag 5 van 10
Toon antwoordVerberg antwoord
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
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?Toon antwoordVerberg antwoord
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
strlen en stackframe twee in je functie render. Je wilt de pointer caption van render in GDB bekijken. Wat doe je?Toon antwoordVerberg antwoord
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
Toon antwoordVerberg antwoord
Vraag 9 van 10
-O0 en -O2. Met -O2 is ook een tussenvariabele niet beschikbaar in de debugger. Welk onderzoek behandelt beide waarnemingen?Toon antwoordVerberg antwoord
Vraag 10 van 10
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?Toon antwoordVerberg antwoord
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
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
unsigned int. Welke waarde slaat unsigned int slots = 0u - 1u; op?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
unsigned int. Invoer levert shift = 32 op. Wat is de status van 1u << shift?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
local terug nadat zij char local[24] = "ready"; heeft gedeclareerd. Waarom is het ongeldig als de aanroepende functie het resultaat leest?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
int a met teken en een positieve int b overloop van a + b zonder die som te evalueren?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
-fsanitize=undefined. Een size_t-vermenigvuldiging loopt modulair terug maar levert geen melding op. Waarom kan dit gebeuren?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
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
assert(user_count > 0); als enige afwijzing van ongeldige aantallen van gebruikers. Productieversies definiëren NDEBUG. Wat moet die bescherming vervangen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
-fsanitize=undefined blijven beide stil, maar de naastgelegen waarde en allocatiegrootte zijn verkeerd. Welk onderzoek pakt beide fouten aan?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
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?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
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
char line[48] door aan read_line(char line[]) op een systeem met pointers van acht bytes. Wat meet sizeof line binnen de functie?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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
errno terug en converteert “64kg” met strtol. De conversie geeft 64 terug en errno blijft nul. Wat moet tot afwijzing leiden?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
strtol, controleert alleen errno, converteert expliciet naar size_t en vermenigvuldigt met de elementgrootte voor de geheugenallocatie. Welke controles moeten aan de allocatie voorafgaan?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
fgets, zonder opgeslagen regelafbrekingsteken. De toepassing vereist één volledig record per regel. Welke afhandeling voorkomt dat een mogelijke rest als nieuw record wordt behandeld?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
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
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Kom je niet uit je eigen opgave? Huiswerkhulp
Kernbegrippen
A
Aanroepoverzicht
bt je het eerste stackframe van de broncode te vinden dat bij je eigen code hoort.Aanroepstapel
main hebben ontvangen.AddressSanitizer
Afkapping
snprintf die minstens zo groot is als de capaciteit geeft aan dat de volledige tekst niet paste.Afsluitend nulteken
ASLR
Assertie
B
Bereikfout
errno stelt een bereikfout van strtol ERANGE in, ook al wordt een grenswaarde teruggegeven.Beveiligingsversterking
Breekpunt
sum += a[i] kun je i bekijken zonder de arraytoegang te evalueren.Buffercapaciteit
C
Code die niet aan de regel voldoet
printf(input) laat gegevens van buitenaf formaatconversies aanleveren en schendt FIO30-C.Compilatiefout
Compilerwaarschuwing
D
Datarace
Debugger
Debuginformatie
-g helpt een debugger de broncoderegel en lokale variabelen bij een gewoon breekpunt te herkennen.Debugversie
-g -O0 maakt een foutieve lus gemakkelijker te volgen per broncode-instructie.Diagnostische compilermelding
%d wijzen en het niet-passende argument van het type double aanwijzen.Door de implementatie bepaald gedrag
char een teken heeft of niet.Dubbele vrijgave
E
Eigenaarschap
realloc met positieve grootte mislukt.Eindpointer
strtol wordt teruggegeven, wijst het eerste niet-geconverteerde teken aan, of het begin van de invoer als geen cijfers zijn verwerkt.Expressie-evaluatie
count af te drukken bekijk je een scalaire waarde; het evalueren van *p probeert toegang via die pointer.F
Falen
free kan het zichtbare falen zijn dat wordt veroorzaakt door een schrijfbewerking veel eerder in de uitvoering.Formaatstring
printf gebruikt %zu voor size_t; gebruikerstekst hoort in een afzonderlijk gegevensargument.Fuzzing
G
Gebruik na vrijgave
Gegevensbreekpunt
Geheugendump
Geheugenveilige programmeertaal
Gelaagde beveiliging
H
Hypothese
I
Impliciete conversie
double aan een int toewijst, valt het gebroken deel weg wanneer de afgekaptе waarde past.Instrumentatie
Integerpromotie
int of unsigned int vóór veel rekenkundige bewerkingen en bitverschuivingen.Invariant
L
Levensduur van een object
M
MemorySanitizer
Minimaal reproducerend voorbeeld
Modulaire terugloop bij gehele getallen zonder teken
N
Naamverberging bij variabelen
count van een binnenste lus kan worden bijgewerkt terwijl de buitenste count ongewijzigd blijft.Niet-geordende neveneffecten
i als ++i aan één aanroep doorgeven mengt een leesbewerking en een wijziging zonder veilige ordening.Niet-gespecificeerd gedrag
Niet-vertrouwde invoer
O
Onbepaalde waarde
int sum; declareert en direct sum += x; gebruikt, lees je een waarde die nooit is geïnitialiseerd.Ongedefinieerd gedrag
Ongeldige pointer naar een niet meer bestaand object
Oplossing die aan de regel voldoet
%s houdt aangeleverde tekst in het gegevensargument van een printf-aanroep.Opruimpad
Optimalisatie
-O2 toont een debugger mogelijk geen afzonderlijke waarde voor een lokale variabele die de compiler heeft verwijderd.Overdraagbaarheid
INT_MAX te gebruiken, voorkom je de aanname dat elke implementatie int dezelfde maximumwaarde geeft.Overlappende geheugenbereiken
memmove vereisen omdat memcpy overlap verbiedt.Overloop bij gehele getallen met teken
int kan één optellen bij INT_MAX geen overdraagbare, gedefinieerde volgende waarde opleveren.P
Postconditie
Preconditie
Productieversie
NDEBUG de asserties verwijdert.Programmeerfout
Q
Quarantaine
R
Regressietest
Rekenen met overloopcontrole
ckd_add van C23 meldt of een som past, zodat het programma een fout expliciet kan afhandelen.Risicoprioriteit
Ruimtelijke geheugenveiligheid
S
Schaduwgeheugen
SEI CERT C Coding Standard
Shellinjectie
system()-string opbouwt uit een ongecontroleerd argument, kan een shell de aangeleverde syntaxis interpreteren.Stackframe
name doorgeeft, maakt het selecteren van het stackframe van die functie na een crash de pointer beschikbaar voor onderzoek.Standaard voor veilig programmeren
Stapsgewijze uitvoering
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 assertie
static_assert in C17 kan een platform afwijzen waarop een vereiste aanname over de grootte van int onwaar is.Stringlengte
strlen("safe") is vier, terwijl opslag van de volledige string vijf bytes vereist.T
Temporele geheugenveiligheid
free schendt de levensduurveiligheid, ook als het adres nog bekend oogt.ThreadSanitizer
Toegang buiten de grenzen
U
UndefinedBehaviorSanitizer
V
Verboden geheugenzone
Voorwaardelijk breekpunt
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.