C Academy · Lektion

Problem med typsäkerhet

Risker med variadiska funktioner.

Lektion 3 av 413 steg

Problem med typsäkerhet är en gratis lektion i C Academy på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för C Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i C Academy innehåller totalt 4 lektioner.

Variadiska argument kringgår typkontroll

Vanliga C-funktioner kontrollerar att argumenten stämmer överens med parametertyperna. Variadiska argument hoppar helt över denna kontroll.

Kompilatorn kan inte verifiera vad Ni skickar efter ..., vilket gör misstag enkla att begå och farliga.

#include <stdio.h>
#include <stdarg.h>

int add(int n, ...) {
    va_list a; va_start(a, n);
    int s = 0;
    for (int i = 0; i < n; i++) s += va_arg(a, int);
    va_end(a);
    return s;
}

int main(void) {
    printf("%d\n", add(2, 3, 4));
    return 0;
}

Fel typ i va_arg

Om Ni läser ett argument med fel typ blir resultatet odefinierat beteende.

Om Ni skickar en int men läser den som en double tolkas orelaterade byte om, vilket kan ge skräpdata eller en krasch.

#include <stdio.h>
#include <stdarg.h>

int read_int(int n, ...) {
    va_list a; va_start(a, n);
    int v = va_arg(a, int);
    va_end(a);
    return v;
}

int main(void) {
    printf("%d\n", read_int(1, 100));
    return 0;
}

Inkompatibla formatsträngar

I funktioner av printf-typ är en felaktig specifikation ett klassiskt fel. printf("%d", 3.14) läser en double som om den vore en int.

Moderna kompilatorer varnar för inkompatibiliteter i bokstavliga formatsträngar, men kan inte upptäcka formatsträngar som byggs vid körning.

#include <stdio.h>

int main(void) {
    double pi = 3.14;
    printf("%f\n", pi);
    return 0;
}

Överraskningar vid heltalskonvertering

Små typer konverteras till int i den variadiska delen. Även om Ni skickar en char måste Ni alltså läsa tillbaka den som en int.

Att läsa den som char med va_arg(a, char) är odefinierat.

#include <stdio.h>
#include <stdarg.h>

void show(int n, ...) {
    va_list a; va_start(a, n);
    int c = va_arg(a, int);
    printf("char is %c\n", c);
    va_end(a);
}

int main(void) {
    show(1, 'A');
    return 0;
}

Float blir double

Ett float-argument konverteras alltid till double i variadiska argument. Ni måste läsa det som double, aldrig som float.

Detta är ett av de vanligaste felen med variadiska argument.

#include <stdio.h>
#include <stdarg.h>

void show(int n, ...) {
    va_list a; va_start(a, n);
    double d = va_arg(a, double);
    printf("%.2f\n", d);
    va_end(a);
}

int main(void) {
    float f = 1.5f;
    show(1, f);
    return 0;
}

Läsa för många argument

Om Er kod anropar va_arg fler gånger än antalet argument som anroparen skickade, läser Ni skräpdata från stacken.

Därför är en korrekt räknare eller sentinel nödvändig.

#include <stdio.h>
#include <stdarg.h>

int sum(int n, ...) {
    va_list a; va_start(a, n);
    int s = 0;
    for (int i = 0; i < n; i++) s += va_arg(a, int);
    va_end(a);
    return s;
}

int main(void) {
    printf("%d\n", sum(3, 1, 2, 3));
    return 0;
}

NULL-pekarsentinel kräver försiktighet

Att använda NULL som sentinel är riskabelt: på vissa plattformar är NULL en 0 av typen int, vilket skiljer sig i storlek från en pekare.

Gör en uttrycklig typkonvertering, till exempel (char *)NULL, för att säkerställa att rätt typ läggs på stacken.

#include <stdio.h>
#include <stdarg.h>

void print_all(const char *first, ...) {
    va_list a; va_start(a, first);
    const char *s = first;
    while (s) { printf("%s\n", s); s = va_arg(a, const char *); }
    va_end(a);
}

int main(void) {
    print_all("a", "b", (char *)NULL);
    return 0;
}

Kompilatorattribut för format

GCC och Clang erbjuder __attribute__((format(printf, 1, 2))) för att tala om för kompilatorn att Er funktion använder formatering av printf-typ.

Då kontrolleras formatsträngarna vid kompilering, vilket upptäcker många fel.

#include <stdio.h>
#include <stdarg.h>

void logf(const char *fmt, ...)
    __attribute__((format(printf, 1, 2)));

void logf(const char *fmt, ...) {
    va_list a; va_start(a, fmt);
    vprintf(fmt, a);
    va_end(a);
}

int main(void) {
    logf("%d items\n", 3);
    return 0;
}

Föredra säkrare alternativ

Eftersom variadiska argument lätt leder till fel bör Ni använda typsäkra lösningar när det är möjligt: skicka en explicit array, en struct eller fasta parametrar.

Använd variadiska argument endast när Ni verkligen behöver funktionalitet av printf-typ.

#include <stdio.h>

int sum_array(const int *a, int n) {
    int s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    return s;
}

int main(void) {
    int nums[] = {1, 2, 3, 4};
    printf("%d\n", sum_array(nums, 4));
    return 0;
}

Dokumentera kontraktet

Eftersom kompilatorn inte kan upprätthålla typerna för variadiska argument är tydlig dokumentation Ert säkerhetsnät.

Ange exakt vilka typer och hur många argument anroparen måste skicka, samt hur listan avslutas.

#include <stdio.h>
#include <stdarg.h>

/* sum_ints(n, ...): pass exactly n int values */
int sum_ints(int n, ...) {
    va_list a; va_start(a, n);
    int s = 0;
    for (int i = 0; i < n; i++) s += va_arg(a, int);
    va_end(a);
    return s;
}

int main(void) {
    printf("%d\n", sum_ints(2, 5, 5));
    return 0;
}

Testa noggrant

Eftersom fel kan förbli oupptäckta bör Ni testa variadiska funktioner med många olika antal och typer av argument.

Gränsfall, till exempel noll argument eller det maximala antalet, avslöjar ofta misstag.

#include <stdio.h>
#include <stdarg.h>

int sum(int n, ...) {
    va_list a; va_start(a, n);
    int s = 0;
    for (int i = 0; i < n; i++) s += va_arg(a, int);
    va_end(a);
    return s;
}

int main(void) {
    printf("%d\n", sum(0));
    printf("%d\n", sum(1, 9));
    return 0;
}

Snabbtest

Testa Er förståelse av riskerna med variadiska typer.

Sammanfattning

Ni har lärt Er om riskerna med variadiska funktioner:

  • Kompilatorn typkontrollerar inte variadiska argument.
  • Fel typer, fel antal och felaktiga sentinelvärden orsakar odefinierat beteende.
  • Kom ihåg konverteringarna: små heltal till int, float till double.
  • Använd kompilatorns formatattribut, dokumentera kontraktet och föredra typsäkra alternativ när det är möjligt.
Gratis att börja

Lär dig C med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
39
Lektioner
144

Vanliga frågor

Är lektionen ”Problem med typsäkerhet” gratis?

Ja – hela texten till ”Problem med typsäkerhet” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i C Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i C Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Problem med typsäkerhet”?

Risker med variadiska funktioner. Ni övar på C Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig C Academy?

Du behöver inga förkunskaper. Utbildningen i C Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Problem med typsäkerhet”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här C Academy-lektionen?

Ja. Varje C Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Makrona i stdarg
  2. Skriv printf-liknande funktioner
  3. Problem med typsäkerhet
  4. Praktiska exempel
← Tillbaka till C Academy