Programmering

Fire vanlige C-programmeringsfeil - og 5 tips for å unngå dem

Få programmeringsspråk kan matche C for ren hastighet og kraft på maskinnivå. Denne uttalelsen var sant for 50 år siden, og den er fremdeles sant i dag. Det er imidlertid en grunn til at programmerere har laget begrepet "footgun" for å beskrive Cs slags kraft. Hvis du ikke er forsiktig, kan C blåse av tærne - eller noen andres.

Her er fire av de vanligste feilene du kan gjøre med C, og fem trinn du kan ta for å forhindre dem.

Vanlig C-feil: Ikke frigjøre malloc-ed minne (eller frigjøre det mer enn en gang)

Dette er en av de store feilene i C, hvorav mange involverer minnehåndtering. Tildelt minne (gjort med malloc funksjon) blir ikke automatisk kastet i C. Det er programmererens jobb å avhende det minnet når det ikke lenger brukes. Unnlater du å frigjøre gjentatte minneforespørsler, og du vil ende opp med en minnelekkasje. Prøv å bruke et område med minne som allerede er frigjort, og programmet vil krasje — eller, verre, vil halte sammen og bli sårbart for et angrep ved hjelp av den mekanismen.

Merk at et minne lekke skal bare beskrive situasjoner hvor hukommelse er antatt å bli frigjort, men er ikke. Hvis et program fortsetter å tildele minne fordi minnet faktisk trengs og brukes til arbeid, kan det være bruk av minnetineffektiv, men strengt tatt er det ikke lekkasje.

Vanlig C-feil: Lesing av en matrise utenfor grensene

Her har vi enda en av de vanligste og farligste feilene i C. En lesning forbi slutten av en matrise kan returnere søppeldata. En skrivning forbi en matrixs grenser kan ødelegge programmets tilstand, eller krasje den fullstendig, eller, verst av alt, bli en angrepsvektor for skadelig programvare.

Så hvorfor er byrden med å sjekke grensene til en matrise overlatt til programmereren? I den offisielle C-spesifikasjonen er å lese eller skrive en matrise utenfor dens grenser "udefinert oppførsel", noe som betyr at spesifikasjonen ikke har noe å si for hva som skal skje. Kompilatoren er ikke engang pålagt å klage på det.

C har lenge foretrukket å gi makten til programmereren selv på egen risiko. En lesing eller skriving utenfor grensene blir vanligvis ikke fanget av kompilatoren, med mindre du spesifikt aktiverer kompilatoralternativer for å beskytte deg mot den. Dessuten kan det godt være mulig å overskride grensen til en matrise ved kjøretid på en måte som selv en kompilatorkontroll ikke kan beskytte seg mot.

Vanlig C-feil: Ikke å sjekke resultatene av malloc

malloc og calloc (for forhåndsnullet minne) er C-biblioteksfunksjonene som skaffer heap allokert minne fra systemet. Hvis de ikke klarer å tildele minne, genererer de en feil. Tilbake i de dager da datamaskiner hadde relativt lite minne, var det en god sjanse for å ringe til dem malloc kanskje ikke lykkes.

Selv om datamaskiner i dag har gigabyte RAM å kaste rundt, er det fortsatt sjansen malloc kan mislykkes, spesielt under høyt minnetrykk eller når du tildeler store minneplater på en gang. Dette gjelder spesielt for C-programmer som "tildeler" en stor blokk med minne fra operativsystemet først og deretter deler den til eget bruk. Hvis den første tildelingen mislykkes fordi den er for stor, kan du kanskje fange det avslaget, redusere tildelingen og justere programmets minneheuristikk i samsvar med dette. Men hvis minnetildelingen mislykkes, kan hele programmet gå oppover.

Vanlig C-feil: Bruk tomrom* for generiske tips til minne

Ved hjelp avtomrom* å peke på hukommelsen er en gammel vane — og en dårlig. Pekere til minne bør alltid være røye *, usignert røye *, elleruintptr_t *. Moderne C-kompilatorsuiter bør tilby uintptr_t som en del av stdint.h

Når det er merket på en av disse måtene, er det klart at pekeren refererer til en minneplassering abstrakt i stedet for til en udefinerte objekttype. Dette er dobbelt så viktig hvis du utfører pekermatematikk. Meduintptr_t * og lignende, størrelseselementet som det pekes på, og hvordan det skal brukes, er entydige. Med tomrom*, ikke så mye.

Unngå vanlige C-feil - 5 tips

Hvordan unngår du disse altfor vanlige feilene når du arbeider med minne, matriser og pekere i C? Husk disse fem tipsene.

Struktur C-programmer slik at eierskap for minne holdes klart

Hvis du nettopp starter en C-app, er det verdt å tenke på hvordan minne tildeles og frigjøres som en av organisasjonens prinsipper for programmet. Hvis det er uklart hvor en gitt minnetildeling frigjøres eller under hvilke omstendigheter, ber du om problemer. Gjør den ekstra innsatsen for å gjøre minnet eierskap så tydelig som mulig. Du vil gjøre deg selv (og fremtidige utviklere) en tjeneste.

Dette er filosofien bak språk som Rust. Rust gjør det umulig å skrive et program som kompileres ordentlig med mindre du tydelig uttrykker hvordan minne eies og overføres. C har ingen slike begrensninger, men det er lurt å ta den filosofien som et ledende lys når det er mulig.

Bruk C-kompilatoralternativer som beskytter mot minneproblemer

Mange av problemene beskrevet i første halvdel av denne artikkelen kan flagges ved å bruke strenge kompilatoralternativer. Nylige utgaver av gcc, for eksempel, gi verktøy som AddressSanitizer (“ASAN”) som et kompileringsalternativ for å sjekke mot vanlige minnestyringsfeil.

Vær advart, disse verktøyene fanger ikke absolutt alt. De er rekkverk; de tar ikke rattet hvis du kjører off-road. Noen av disse verktøyene, som ASAN, pålegger også kompilering og kjøretidskostnader, så det bør unngås i versjonskonstruksjoner.

Bruk Cppcheck eller Valgrind for å analysere C-kode for minnelekkasjer

Der kompilatorene selv kommer til kort, trer andre verktøy inn for å fylle gapet - spesielt når det gjelder å analysere programadferd under kjøretid.

Cppcheck kjører statisk analyse på C-kildekode for å se etter vanlige feil i minnestyring og udefinert oppførsel (blant annet).

Valgrind tilbyr en hurtigbuffer med verktøy for å oppdage minne- og trådfeil i kjørende C-programmer. Dette er langt mer kraftfullt enn å bruke analyse av kompileringstid siden du kan utlede informasjon om programmets oppførsel når det faktisk er live. Ulempen er at programmet kjører med en brøkdel av normal hastighet. Men dette er generelt greit for testing.

Disse verktøyene er ikke sølvkuler, og de fanger ikke alt. Men de jobber som en del av en generell defensiv strategi mot minnehåndtering i C.

Automatiser C-minnehåndtering med en søppeloppsamler

Siden minnefeil er en iøynefallende kilde til C-problemer, er det en enkel løsning: Ikke administrer minne i C manuelt. Bruk en søppeloppsamler.

Ja, dette er mulig i C. Du kan bruke noe som Boehm-Demers-Weiser søppeloppsamleren for å legge til automatisk minnehåndtering til C-programmer. For noen programmer kan bruk av Boehm-samleren til og med øke hastigheten. Det kan til og med brukes som lekkasjedeteksjonsmekanisme.

Den viktigste ulempen med Boehm søppeloppsamleren er at den ikke kan skanne eller frigjøre minne som bruker standard malloc. Den bruker sin egen tildelingsfunksjon, og den fungerer bare på minne du tildeler spesifikt med den.

Ikke bruk C når et annet språk gjør det

Noen mennesker skriver i C fordi de virkelig liker det og synes det er fruktbart. I det store og hele er det imidlertid best å bare bruke C når du må, og da bare sparsomt, for de få situasjonene der det virkelig er det ideelle valget.

Hvis du har et prosjekt der utførelsesytelsen vil bli begrenset hovedsakelig av I / O eller diskadgang, vil det sannsynligvis ikke gjøre det raskere å skrive det i C på de måtene som betyr noe, og sannsynligvis bare gjøre det mer feilutsatt og vanskelig å vedlikeholde. Det samme programmet kan godt være skrevet i Go eller Python.

En annen tilnærming er å bruke C kun for de virkelig ytelseskrevende deler av appen, og et mer pålitelig, om enn langsommere språk for andre deler. Igjen kan Python brukes til å pakke inn C-biblioteker eller tilpasset C-kode, noe som gjør det til et godt valg for de mer kokeplatekomponentene som kommandolinjealternativhåndtering.

$config[zx-auto] not found$config[zx-overlay] not found