Programmering

4 almindelige C-programmeringsfejl - og 5 tip til at undgå dem

Få programmeringssprog kan matche C for ren hastighed og maskinniveau. Denne erklæring var sand for 50 år siden, og den er stadig sand i dag. Der er dog en grund til, at programmører opfandt udtrykket "footgun" for at beskrive C's slags magt. Hvis du ikke er forsigtig, kan C sprænge tæerne af dig - eller en andens.

Her er fire af de mest almindelige fejl, du kan lave med C, og fem trin, du kan tage for at forhindre dem.

Almindelig C-fejl: Frigør ikke malloc-ed hukommelse (eller frigøre det mere end en gang)

Dette er en af ​​de store fejl i C, hvoraf mange involverer hukommelsesstyring. Tildelt hukommelse (udført ved hjælp af malloc funktion) bortskaffes ikke automatisk i C. Det er programmørens job at bortskaffe den hukommelse, når den ikke længere bruges. Undlad at frigøre gentagne anmodninger om hukommelse, og du vil ende med en hukommelseslækage. Prøv at bruge en hukommelsesregion, der allerede er frigivet, og dit program går ned - eller, værre, vil halte sammen og blive sårbart over for et angreb ved hjælp af denne mekanisme.

Bemærk, at en hukommelse lække skal kun beskrive situationer, hvor hukommelse er antages at blive befriet, men er det ikke. Hvis et program fortsat tildeler hukommelse, fordi hukommelsen faktisk er nødvendig og bruges til arbejde, kan det være hukommelsenineffektiv, men strengt taget er det ikke lækage.

Almindelig C-fejl: Læsning af en matrix uden for grænserne

Her har vi endnu en af ​​de mest almindelige og farlige fejl i C. En læsning forbi slutningen af ​​en matrix kan returnere skraldata. En skrivning forbi en matrixs grænser kan ødelægge programmets tilstand eller ødelægge den fuldstændigt eller, værst af alt, blive en angrebsvektor for malware.

Så hvorfor overlades programmøren til byrden at kontrollere en matrixs grænser? I den officielle C-specifikation er læsning eller skrivning af en matrix ud over dens grænser "udefineret adfærd", hvilket betyder, at spec ikke har noget at sige om, hvad der skal ske. Compileren er ikke engang forpligtet til at klage over det.

C har længe foretrukket at give magt til programmøren selv på egen risiko. En out-of-bounds læsning eller skrivning er typisk ikke fanget af compileren, medmindre du specifikt aktiverer kompilatorindstillinger for at beskytte mod det. Hvad mere er, det kan meget vel være muligt at overskride grænserne for et array ved kørsel på en måde, som selv en compiler-kontrol ikke kan beskytte sig mod.

Almindelig C-fejl: Kontrollerer ikke resultaterne af malloc

malloc og calloc (til forud-nulstillet hukommelse) er C-biblioteksfunktionerne, der henter heap-allokeret hukommelse fra systemet. Hvis de ikke er i stand til at allokere hukommelse, genererer de en fejl. Tilbage i de dage, hvor computere havde relativt lidt hukommelse, var der en rimelig chance for et opkald til malloc måske ikke lykkes.

Selvom computere i dag har gigabyte RAM at kaste rundt, er der stadig chancen malloc kan mislykkes, især under højt hukommelsestryk eller ved tildeling af store hukommelsesplader på én gang. Dette gælder især for C-programmer, der "tildeler" en stor hukommelsesblok fra operativsystemet først og derefter deler den til eget brug. Hvis den første tildeling mislykkes, fordi den er for stor, kan du muligvis fælde dette afslag, nedskære tildelingen og indstille programmets hukommelsesforbrugsheuristikker i overensstemmelse hermed. Men hvis tildelingen af ​​hukommelse mislykkes, kan hele programmet gå i stykker.

Almindelig C-fejl: Brug ugyldig* til generiske henvisninger til hukommelsen

Ved brug afugyldig* at pege på hukommelse er en gammel vane - og en dårlig. Henvisninger til hukommelse skal altid være char *, usigneret char *, elleruintptr_t *. Moderne C-kompilatorsuiter skal give uintptr_t som en del af stdint.h

Når det er mærket på en af ​​disse måder, er det klart, at markøren refererer til en hukommelsesplacering abstrakt i stedet for til en udefineret objekttype. Dette er dobbelt så vigtigt, hvis du udfører markørmatematik. Meduintptr_t * og lignende, hvor størrelseselementet peges på, og hvordan det vil blive brugt, er entydige. Med ugyldig*, ikke så meget.

Undgå almindelige C-fejl - 5 tip

Hvordan undgår du disse alt for almindelige fejl, når du arbejder med hukommelse, arrays og markører i C? Husk disse fem tip.

Struktur C-programmer, så ejerskab til hukommelse holdes klart

Hvis du lige starter en C-app, er det værd at tænke på, hvordan hukommelse allokeres og frigives som en af ​​de organisatoriske principper for programmet. Hvis det er uklart, hvor en given hukommelsestildeling frigøres, eller under hvilke omstændigheder, beder du om problemer. Gør den ekstra indsats for at gøre hukommelsesejerskab så klart som muligt. Du gør dig selv (og fremtidige udviklere) en tjeneste.

Dette er filosofien bag sprog som Rust. Rust gør det umuligt at skrive et program, der kompileres korrekt, medmindre du tydeligt udtrykker, hvordan hukommelse ejes og overføres. C har ingen sådanne begrænsninger, men det er klogt at anvende denne filosofi som et ledende lys, når det er muligt.

Brug C-kompilatorindstillinger, der beskytter mod hukommelsesproblemer

Mange af de problemer, der er beskrevet i første halvdel af denne artikel, kan markeres ved hjælp af strenge kompilatorindstillinger. Seneste udgaver af gccfor eksempel at levere værktøjer som AddressSanitizer (“ASAN”) som en kompileringsmulighed for at kontrollere mod almindelige hukommelsesstyringsfejl.

Vær advaret, disse værktøjer fanger ikke absolut alt. De er gelænder; de griber ikke rattet, hvis du kører off-road. Nogle af disse værktøjer, som ASAN, pålægger også kompilering og runtime-omkostninger, så det bør undgås i udgivelsesbygninger.

Brug Cppcheck eller Valgrind til at analysere C-kode for hukommelseslækage

Hvor kompilatorerne selv mangler, træder andre værktøjer ind for at udfylde hullet - især når det kommer til at analysere programadfærd under kørsel.

Cppcheck kører statisk analyse på C-kildekode for at se efter almindelige fejl i hukommelsesstyring og udefineret adfærd (blandt andet).

Valgrind leverer en cache med værktøjer til at opdage hukommelses- og trådfejl i kørende C-programmer. Dette er langt mere kraftfuldt end at bruge kompileringstidsanalyse, da du kan udlede oplysninger om programmets adfærd, når det faktisk er live. Ulempen er, at programmet kører med en brøkdel af sin normale hastighed. Men dette er generelt fint til test.

Disse værktøjer er ikke sølvkugler, og de fanger ikke alt. Men de arbejder som en del af en generel defensiv strategi mod hukommelsesfejladministration i C.

Automatiser C-hukommelsesstyring med en affaldssamler

Da hukommelsesfejl er en iøjnefaldende kilde til C-problemer, er her en let løsning: Du må ikke administrere hukommelse i C manuelt. Brug en affaldssamler.

Ja, dette er muligt i C. Du kan bruge noget som Boehm-Demers-Weiser-affaldssamleren til at tilføje automatisk hukommelsesstyring til C-programmer. For nogle programmer kan brugen af ​​Boehm-samleren endda fremskynde tingene. Det kan endda bruges som en lækagedetekteringsmekanisme.

Den største ulempe ved Boehm-affaldssamleren er, at den ikke kan scanne eller frigøre hukommelse, der bruger standard malloc. Det bruger sin egen allokeringsfunktion, og det fungerer kun på hukommelse, du tildeler specifikt med det.

Brug ikke C, når et andet sprog gør det

Nogle mennesker skriver i C, fordi de virkelig nyder det og finder det frugtbart. Alt i alt er det dog bedst kun at bruge C, når du skal, og derefter kun sparsomt, i de få situationer, hvor det virkelig er det ideelle valg.

Hvis du har et projekt, hvor udførelsesydelse primært begrænses af I / O eller diskadgang, vil det sandsynligvis kun gøre det hurtigere på de måder, der betyder noget, at skrive det i C og skrive det i C mere sandsynligt og vanskeligt at skrive opretholde. Det samme program kunne godt være skrevet i Go eller Python.

En anden tilgang er at bruge C kun til den virkelig præstationsintensive dele af appen og et mere pålideligt, omend langsommere sprog for andre dele. Igen kan Python bruges til at pakke C-biblioteker eller brugerdefineret C-kode, hvilket gør det til et godt valg for de mere kedelpladekomponenter som kommandolinjemulighedshåndtering.