Beetje Coderen

Les 3

Typen en geheugen in C

int, long, char, float en double: hoeveel geheugen een waarde inneemt en wat er gebeurt als hij niet past.

⏱ 45 minuten leestijd + oefenen

🎯 Na deze les kun je

  • de basistypen van C kiezen voor een gegeven situatie
  • met sizeof bepalen hoeveel bytes een type inneemt
  • waarden afdrukken en inlezen met de juiste printf/scanf-specifiers
  • integer overflow herkennen

In Python schrijf je temperatuur = 21.5 en Python regelt de rest. In C moet je bij elke variabele zeggen wat voor soort waarde erin komt. Dat type bepaalt twee dingen: hoeveel bytes geheugen de variabele krijgt en hoe die bytes gelezen worden.

Dat klinkt als extra werk, en dat is het ook. Maar het is precies waarom C zo dicht bij de machine zit: je weet altijd hoe je gegevens in het geheugen liggen.

De basistypen

int leeftijd = 34;          /* geheel getal */
long inwoners = 17900000L;  /* groter geheel getal */
char letter = 'A';          /* één teken (eigenlijk een klein getal) */
float prijs = 2.49f;        /* kommagetal, enkele precisie */
double afstand = 384400.0;  /* kommagetal, dubbele precisie */
Type Typische grootte (64-bit Linux) Gebruik
char 1 byte één teken of een klein getal
int 4 bytes gewone gehele getallen (±2,1 miljard)
long 8 bytes grote gehele getallen
float 4 bytes kommagetal, ~7 cijfers precisie
double 8 bytes kommagetal, ~15 cijfers precisie

Elk geheel type bestaat ook als unsigned (alleen ≥ 0, dubbel zo groot bereik naar boven). Gebruik voor kommagetallen standaard double; float is alleen zinvol als geheugen echt krap is.

De groottes in de tabel zijn typisch, niet gegarandeerd. De C-standaard zegt alleen dat een int minstens 16 bits is. Op een microcontroller (cursus Embedded C) is int vaak 2 bytes. Wil je een vaste grootte, gebruik dan int32_t en vrienden uit <stdint.h>.

sizeof

Met sizeof vraag je de compiler hoeveel bytes iets inneemt:

#include <stdio.h>

int main(void) {
    printf("char:   %zu\n", sizeof(char));
    printf("int:    %zu\n", sizeof(int));
    printf("long:   %zu\n", sizeof(long));
    printf("double: %zu\n", sizeof(double));
    return 0;
}

sizeof levert een size_t op; die druk je af met %zu. sizeof(char) is per definitie 1.

printf en scanf

printf weet niet zelf welk type je meegeeft. Jij vertelt het met een format specifier:

Specifier Type
%d int
%ld long
%u unsigned int
%c char
%f double (bij printf ook float)
%.1f double met 1 decimaal
%zu size_t

scanf leest invoer en moet weten waar de waarde heen moet. Daarom staat er een & voor de variabele (wat dat precies is, zie je in les 6):

#include <stdio.h>

int main(void) {
    double celsius;
    printf("Temperatuur in graden Celsius: ");
    if (scanf("%lf", &celsius) != 1) {
        printf("Dat is geen getal.\n");
        return 1;
    }
    printf("%.1f C = %.1f F\n", celsius, celsius * 9.0 / 5.0 + 32.0);
    return 0;
}

Let op het verschil: bij scanf is %lf voor double en %f voor float. scanf geeft terug hoeveel waarden het gelezen heeft; controleer dat altijd.

Integer overflow

Voorspel de uitvoer:

#include <stdio.h>
#include <limits.h>

int main(void) {
    unsigned char teller = 255;
    teller = teller + 1;
    printf("teller: %d\n", teller);
    printf("INT_MAX: %d\n", INT_MAX);
    return 0;
}
teller: 0
INT_MAX: 2147483647

Een unsigned char kan 0 t/m 255 bevatten. 255 + 1 past niet en "loopt rond" naar 0. Bij unsigned typen is dat netjes gedefinieerd gedrag. Bij signed typen (int, long) is overflow ongedefinieerd gedrag (undefined behavior): de compiler mag aannemen dat het nooit gebeurt, en je programma kan dan van alles doen. Python kent dit probleem niet: daar groeien gehele getallen gewoon mee.

Typische fouten

  • Verkeerde specifier: printf("%d", 3.5) drukt onzin af. Met -Wall krijg je format '%d' expects argument of type 'int', but argument 2 has type 'double'.
  • & vergeten bij scanf: scanf("%d", leeftijd) crasht vaak. Ook hier waarschuwt -Wall.
  • Gehele deling: 9 / 5 is 1, niet 1.8. Schrijf 9.0 / 5.0 zodra je een kommagetal wilt. Daar komen we in les 4 op terug.

Oefeningen

  1. Breid het sizeof-programma uit met float, unsigned int en long long.
  2. Een int op jouw machine is 4 bytes. Wat is INT_MAX + 1 volgens de standaard? Zoek ook uit wat UINT_MAX + 1u is.
  3. Schrijf een programma dat een lengte in centimeters inleest (geheel getal) en die als meters met twee decimalen afdrukt.
Modelantwoord
  1. printf("float: %zu\n", sizeof(float)); enzovoort. Typisch: float 4, unsigned int 4, long long 8.
  2. INT_MAX + 1 is signed overflow en dus ongedefinieerd gedrag: er is geen "juist" antwoord. UINT_MAX + 1u is 0: unsigned rekenen loopt netjes rond.
  3. Een mogelijke uitwerking:
#include <stdio.h>

int main(void) {
    int cm;
    if (scanf("%d", &cm) != 1) {
        return 1;
    }
    printf("%.2f m\n", cm / 100.0);
    return 0;
}

Let op 100.0: met cm / 100 krijg je een gehele deling.

Lab: temperatuurtabel met de juiste typen

  1. Schrijf tabel.c dat een tabel van -10 tot en met 40 graden Celsius afdrukt in stappen van 5, met daarnaast Fahrenheit op één decimaal.
  2. Kies voor elke variabele een passend type en gebruik de juiste specifiers.
  3. Compileer zonder waarschuwingen met -Wall -Wextra -Werror.
  4. beetje check c-cpp-les3

Samenvatting

  • Elke variabele heeft een type; dat bepaalt grootte en betekenis van de bytes.
  • sizeof vertelt hoeveel bytes iets inneemt; groottes verschillen per platform.
  • printf en scanf hebben de juiste specifier nodig; scanf wil een &.
  • Unsigned overflow loopt rond; signed overflow is ongedefinieerd gedrag.

Volgende les: operatoren en control flow, inclusief de beruchte gehele deling.

Lab

Temperatuurtabel met de juiste typen

💻 Dit lab doe je op je eigen computer met Docker. Start de lab-container met (geeft de opdracht een eigen startcommando, gebruik dan dat):

$ docker run -it --rm registry.wizg.it/webhosting/beetje-labs:c-cpp-les3

Volg daarna de stappen van de opdracht hierboven. Nog geen Docker? Zie les 14 van Een beetje Linux & Docker. Voor dit lab is er geen live lab in de browser.

Klaar? Controleer je werk met:

$ beetje check c-cpp-les3

📝 Quiz: typen en geheugen in C

5 vragen · 70% nodig om te slagen

1. Welk type kies je standaard voor een kommagetal?
2. Met welke specifier lees je een double in met scanf?
3. Wat is de waarde van een unsigned char met 255 na `+ 1`?
4. Wat gebeurt er bij overflow van een signed int?
5. Waarom staat er een & in `scanf("%d", &leeftijd)`?