# Programmeren in C/Datatypes

Om gegevens te kunnen opslaan en bewerken, kent C een aantal datatypes die onderverdeeld kunnen worden in twee klassen:
* Enkelvoudige types (basic types), die één enkele waarde kunnen bevatten.
* Samengestelde types (aggregate types), die bestaan uit een of meer andere types die op hun beurt ook samengesteld kunnen zijn.

## Enkelvoudige datatypes

    char - character
    int - integer
    float - floating point
    double - double floating point
    void - valueless
    bool - boolean
    wchar_t - wide character
    pointers

## Samengestelde types

Hieronder vallen arrays, structures, unions.
In dit hoofdstuk zullen alleen de enkelvoudige types worden behandeld en alleen voor zover ze standaard zijn. Arrays, structs, unions zullen in afzonderlijke hoofdstukken worden behandeld, want deze hebben wat meer voeten in aarde.

## int

Het meest gebruikte type is int, waarmee een geheel getal weergegeven wordt. Dit lijkt echter een stukje simpeler dan het in werkelijkheid is, want, zoals al in de inleiding werd verteld, de taal C mag geen aannames doen over de hardware waarop het programma uiteindelijk zal werken. C Integers hebben dan ook, met uitzondering van een short int, geen vaste afmetingen. Uitsluitend het minimale aantal bits is gespecificeerd.
Hierover bestaan, vooral bij hobby-programmeurs en novicen nogal eens wat misverstanden. Zo hoort men vaak de stelling "een long is 32 bits", wat een gevaarlijke misvatting is. Zodra de hobby-programmeur zich een 64-bits machine aanschaft, zal hij tot zijn verbijstering merken dat een long int daar vaak 64 bits heeft.
Een andere misvatting is dat signed integers altijd in 2-complement vorm staan. Dit is weliswaar in de regel het geval, maar het wordt geenzins door de standaard vereist. Moraal van het verhaal, neem niets zomaar aan, want dat is een recept voor rampen.
Al deze typen komen ook in een "versie zonder teken" voor. De unsigned versies hebben net zoveel bits als de signed versies, maar negatieve getallen kunnen niet worden weergegeven, maar het bereik op de positieve as is tweemaal zo groot.
Een "normale" integer heeft in deze taal een minimale breedte van 16 bits, hoewel de meeste huis-, tuin- en keuken-pc programmeer omgevingen voor beide typen 32 bits hanteren. Dit heeft aanleiding gegeven tot veel misverstanden onder hobby-programmeurs en de sarcastische slogan "all the world is 32 bit". De fouten die hiermee worden gemaakt, door aan te nemen dat type X altijd N bits telt of dat type X en type Y even breed zijn, komen aan het licht zodra een van beide typen afwijkt, bijvoorbeeld omdat men is overgeschakeld naar een 64-bits implementatie, waar een long integer 64 bits bevat. Dit verschijnsel is een vrij veelvoorkomende bron van bugs. Met de sizeof operator laat zich echter eenvoudig achterhalen welke definities een specifieke compiler gebruikt en kan de software zonder deze aannames geschreven worden.

## char

Een char is een type dat bedoeld is om karakters in op te slaan. Het type is breed genoeg om alle karakters in de execution character set als positieve waarde op te slaan. De waarde van alle andere karakters is afhankelijk van de implementatie.
Net als bij het type int en zijn afgeleiden, heersen over het type char ook de nodige misverstanden. Het is bijvoorbeeld niet zo dat een char altijd een byte is. Dit is weliswaar vaak het geval, maar wordt geenzins door de standaard vereist. Wel heeft een char minimaal 7 bits (geen 8!). Evenmin specificeert de standaard of een char met teken of zonder wordt opgeslagen. Ook dit is afhankelijk van de implementatie.
Om het al dan niet gebruiken van een teken-bit expliciet te maken, kunnen, net als bij de integers, de keyword signed en unsigned worden gebruikt, hetgeen al met al drie verschillende types char oplevert.
Het is mogelijk een char als "tiny int" te gebruiken, maar meestal levert dit meer last dan gemak op. Veelal moet extra object-code worden gegenereerd om de conversie van char naar int te bewerkstelligen (wat het programma groter maakt in plaats van kleiner) en de teken-expansie kan rare effecten opleveren, vooral omdat de standaard in het midden laat of char wel of geen teken heeft.

## float, double en long double

De typen float, double en long double worden gebruikt om getallen met een drijvende komma op te slaan. Hoe de representatie van deze getallen plaatsvindt, is niet gespecificeerd en daarmee afhankelijk van de compiler. Het enige dat wel gespecificeerd is, is dat een double een grotere precisie en bereik heeft dan een float en een long double een grotere precisie en bereik heeft dan een double. Over het algemeen kan gezegd worden dat deze drie de volgende standaard elementen hebben. Het loont de moeite om, als men programma's met floating-point-getallen schrijft, te informeren naar de implementatie die de compiler gebruikt (ofwel: "RTFM").
Een getal wordt dan weergegeven als
    $x=s\cdot m\cdot r^{e}.$
Daarin zijn:
Verder is een bijzondere waarde gedefinieerd die bekend staat als NaN (Not a Number). Dit symbool wordt gebruikt als een berekening geen numeriek resultaat heeft, zoals b "268/0". NaN kent twee verschillende smaken:
* Quiet NaN, De expressie resulteert in een NaN, maar verder gebeurt er niets.
* Signalling NaN, Zodra deze variant voorkomt wordt een uitzondering gegenereerd (an exception is raised). Dit heeft in de regel een voortijdig afbreken van het programma tot gevolg.
Het is belangrijk te begrijpen dat een floating point-getal niet hetzelfde is als het wiskundige concept reëel getal (element van $\mathbb {R}$). De reële getallen vormen een continuüm, wat wil zeggen dat voor elke twee reële getallen a en b, met a < b, een getal x bestaat zodat geldt:
    $a<x<b$
Floating point getallen missen deze eigenschap, en vormen slechts een eindig deel van de reële getallen. Dit heeft tot gevolg dat een floating point variable niet elke denkbare waarde kan hebben en daarom niet altijd exact de weergave is van de waarde van de variabele. Vooral onder beginners leidt dit vaak tot problemen. Bijvoorbeeld…
C-code:
Bovenstaande code zal niet het resultaat opleveren dat de naïeve programmeur ervan verwacht, ondanks het feit dat 3*(1/3) wel degelijk 1 is. Dit ligt aan het feit dat een binaire representatie van 1/3 als floating point onmogelijk is en er dus een fout wordt geïntroduceerd. Een heel kleine fout weliswaar, afhankelijk van de precisie van double op het platform in kwestie, maar genoeg om de is gelijk-operator te ondermijnen. Over het algemeen kan men stellen dat het gebruik van de == (is-gelijk) operator met twee floating-point getallen uit den boze is.
De gebruikelijke oplossing ziet er (ongeveer) als volgt uit.
C-code:
En "Hey, Presto!". De code werkt zoals het de bedoeling is omdat we nu rekening houden met de inherente fout die het gebruik van floating-point getallen oplevert. Met behulp van "fabs(x*3 - 1.0) < EPSILON" wordt het absolute verschil uitgerekend en als dit kleiner is dan een voorgedefinieerd maximum (EPSILON) zijn de twee waarden "gelijk" of in ieder geval "nagenoeg gelijk". In de wandeling staat deze techniek ook wel bekend als "close-enough comparison". Een goede waarde voor EPSILON hangt af van de toepassing en de implementatie van de compiler (dus "RTFM", nogmaals).

## Representatie

Alle types getallen hebben een signed en een unsigned variant. Dit sleutelwoord wordt voor de variabele geplaatst. Bijvoorbeeld:
C-code:
Dit declareert een integer zonder tekenbit, dit wil zeggen dat een extra bitpositie vrijkomt voor nuttigere informatie en dat tweemaal zo grote getallen opgeslagen kunnen worden.
Als je een variabele van het type unsigned maakt, kan hij twee keer zo grote getallen bevatten als signed, maar alleen positieve. Dit komt doordat je het eerste bit (het teken bit) opoffert, je gaat ervan uit dat de getallen standaard positief zijn. En vermits per extra bit het aantal mogelijke waarden verdubbeld kan men dus dubbel zo grote getallen opslaan.
Een makkelijke manier om te weten komen hoeveel bytes gereserveerd zijn voor een bepaald datatype kan met behulp van de sizeof. Deze wordt tijdens het compileren vervangen door de grootte die gereserveerd wordt voor het datatype (in aantal bytes).
C-code:
De uitvoer op een 32 bit Linux systeem is (uit deze uitvoer volgt automatisch dat het een 32 bit-systeem is. Kijk maar naar de grootte van een pointer, deze is 4 byte, dus 32 bit):

## enum

Een enum (kort voor enumerated type) is een "opgesomd type". Dat wil zeggen dat alle mogelijke waarden voor zo'n type expliciet worden opgesomd en andere waarden domweg niet bestaan. Dit kan bijzonder handig zijn in gevallen waar slechts een beperkt aantal waarden nodig is.
C-code:
Deze code definieert een datatype 'kleur' als een opsomming met drie mogelijke waarden. Aan elk van deze waarden wordt een numerieke waarde toegekend, maar welke waarde dit precies is, is hier ongedefinieerd. Als het wenselijk is dat deze drie een specifieke waarde hebben, kan dat ook:
C-code:
De waarde Rood is nu equivalent aan 0, groen aan 1 en blauw aan 2. Let wel dat het daarmee nog steeds geen int is, maar een afzonderlijk datatype.
C-code:
Het type dat de enum representeert is sterk van de compiler afhankelijk. Dit kan een int zijn, maar het type is compiler-afhankelijk. De toekenning "k=21231" neemt aan dat dit type groot genoeg is om 21231 te bevatten, maar er bestaat geen garantie dat dat inderdaad zo is. Bovendien is het doel van een enum een type te definieren dat alleen bepaalde waarden kan bevatten, als we daar dan allerlei andere waarden in gaan stoppen, zetten we onze medeprogrammeurs op het verkeerde been en de fouten zijn voorgeprogrammeerd. Niet goed, niet doen, dus.

## void

Het type void ("leeg") is een wat bijzonder geval aangezien dit type geen waarde aanduidt. Men kan dan ook geen variabele met het type void declareren. Het type wordt voornamelijk gebruikt om aan te geven dat een functie geen waarde teruggeeft of geen argumenten verwacht. Het is niet mogelijk een variabele van het type void te declareren, om de hele simpele reden dat dit type geen afmeting heeft en in 0 bits laten zich nu eenmaal geen waarden opslaan.
C-code:

## bool

Een boolean variabele heeft in principe twee mogelijke waarden, nl. TRUE (waar) en FALSE (onwaar). Door de declaratie
    bool boolVar;
wordt een boolean variabele 'boolVar' gedeclareerd. Deze kan bijvoorbeeld gebruikt worden in een if-statement:
    if (boolVar) {opdracht;}
De opdracht zal uitgevoerd worden als 'boolVar' de waarde TRUE heeft. Bij de waarde FALSE wordt de opdracht overgeslagen.

## Pointers

Een pointer ("wijzer") in C is, simpel gezegd, een variabele die een geheugen-adres kan bevatten. Een pointer wijst dus naar een geheugengebied waar een bepaalde waarde hoort te staan. Of deze waarde er inderdaad staat, is overigens volledig de verantwoordelijkheid van de programmeur. Pointers zijn, net als integers, floating-point types scalars. Dat wil zeggen dat de normale arithmatische bewerkingen (zoals optellen en aftrekken) gebruikt kunnen worden, zij het dat deze bewerkingen een wat bijzondere opvatting over het resultaat van een optelling hebben.
Pointers zijn geen zelfstandige types. Ze komen altijd voor in combinatie met een ander type zoals int, float of zelfs een struct, array of zelfs een functie. Een pointer wordt gedeclareerd door het type waarnaar de pointer moet wijzen, met de naam van de pointer voorafgegaan door *. Zo declareert
    int *ipnt;
    struct *strpnt;
de pointers 'ipnt' die naar een integer wijst en 'strpnt' die naar een structure wijst. Deze vorm van declareren lijkt enigszins verwarrend, aangezien het lijkt alsof een integer gedeclareerd wordt. Wel is het zo dat *ipnt inderdaad de integer is waarnaar ipnt wijst. De declaratie mag ook als volgt:
    int * ipnt;
    struct * strpnt;
Ook is het toegestaan de declaratie als volgt te schrijven:
    int* ipnt;
    struct* strpnt;
waardoor het meer lijkt op de declaratie van de varabele 'ipnt' van het type int*, een pointer naar een integer.
Een bijzonder type pointer is de void pointer, een "wijzer naar niets". Dit houdt niet in dat het geheugengebied waarnaar dit type wijst leeg is, maar het type ervan is niet gespecificeerd. In de praktijk betekent dit dat elke willekeurige pointer aan een void pointer kan worden toegekend, maar dat een void pointer nooit zonder expliciete type-cast aan een ander type pointer kan worden toegekend. Het gebruik van void pointers kan daarom bijzonder handig zijn, maar (vanwege de expliciete cast) is het mogelijk hiermee zelfs het zeer tolerante type-checking systeem van C volledig buiten werking gesteld kan worden.
C-code:
Om aan te geven dat een pointer nergens naar wijst, kan een 0 (of, wat netter, NULL) aan die pointer worden toegekend. Het resultaat ervan is dat de pointer een null-pointer wordt, een bijzondere waarde die, als de pointer wordt gevolgd (gedereferenced in vakjargon) veelal in een exceptie resulteert. Officieel is dit echter niet meer dan undefined behavior, ongedefinieerd gedrag. Dit verschijnsel is een veelvoorkomende oorzaak van programma-crashes. De representatie van een null-pointer is overigens volledig compiler afhankelijk, de enige voorwaarde die de standaard stelt is dat het geen geldige waarde voor een pointer mag zijn.
C-pointers zijn bijzonder krachtig en daarmee ook bijzonder gevaarlijk, vooral in combinatie met ad-hoc typecasts. De reden hiervoor is dat met behulp van pointers ieder gebied in het geheugen kan worden geadresseerd buiten de controle van de compiler om. Slechts de hardware in combinatie met het Operating System verbieden toegang tot bepaalde (reeksen) adressen, de C compiler let daar volstrekt niet op, maar laat het aan de programmeur over om te zorgen dat de adressen geldig en toegankelijk zijn.

## Operatoren
### Bitgerichte operatoren

Een bitgerichte (Engels: bitwise) operator is een operator (zoals plus en min er ook zijn) die toepassing kent op 'bit' niveau van de operatoren. Al de bitgerichte operatoren leunen dicht aan bij operaties die een chip meestal in hardware doet en dit is ook de context waarin deze operatoren het meest gebruikt worden. Volgende operatoren bestaan:
| Operator | Beschrijving |
| && | logische AND |
| || | logische OR |
| != | logische NOT |
| ^ | logische XOR |
| << | logische shift naar links |
| >> | logische shift naar rechts |
Merk op dat alle operatoren eveneens bestaan in hun assignatie variant (&=, |=, !=, ^=, <<= en >>=) hun werking is volkomen analoog met voorgaande operatoren zoals bijvoorbeeld + en +=. Vervolgens zullen alle bitgerichte operatoren besproken en toegelicht worden.

#### AND

De logische AND plaatst een bit op 1 indien alle bits in beide operanden (in gelijke posities) eveneens 1 zijn en 0 in andere gevallen. Zo zal
C-code:
Gelijk zijn aan 0xf000. In het vorige voorbeeld werd 0xff00 gebruikt om het hexadecimaal getal (0x geeft aan dat hetgeen dat volgt hexadecimaal genoteerd zal zijn) welke 16 bit groot is op te slaan in een unsigned short. Unsigned omdat puur op bits gewerkt wordt een een tekenbit dus niet relevant is en een short omdat dit op een standaard Linux machine exact 16 bit groot is. In binair zouden de getallen a en b er respectievelijk als 1111111100000000 en 1111000011110000 uitzien. De logische functie toepassen geeft enkel een 1 op de posities waar zowel a en b tegelijk een 1 bevatten. Vervolgens wordt printf gebruikt om het geheel hexadecimaal (%x) op het scherm te plaatsen.
Meestgebruikte toepassing van AND is om te kijken of een bepaalde vlag gezet is:
C-code:
In dit voorbeeld wordt een statusregister van 8 bit ingevuld waarvan de op één na minst significante bit als betekenis heeft dat nieuwe data ontvangen is. Wanneer dit statusregister een AND ondergaat met het bitmasker dat enkel deze bit bevat dient het resultaat exact gelijk te zijn aan dit bitmasker indien er data ontvangen is, anders is deze bit niet gezet. Een tweede veelgebruikte toepassing is om een aantal bits laag te zitten. Wanneer men een waarde AND met 0x000f worden de vier minst significante bits overgenomen van de oorspronkelijke operator en worden de 12 meest significante bits zowieso op nul geplaatst.

#### OR

De logische OR plaatst een bit op 1 indien minstens één bit in beide operanden (op dezelfde positie) eveneens 1 is en 0 in andere gevallen (dus enkel nul indien ze beide nul zijn . Zo zal
C-code:
Een resultaat geven van 0xfff0.
Een OR wordt het meest gebruikt om ervoor te zorgen dat bepaalde bits aanstaan, bijvoorbeeld:
C-code:
Hierbij wordt een byte als commando ergens heen gestuurd, de minst significante bit wil zeggen dat de temperatuur gecontroleerd dient te worden de op één na laagste bit wil zeggen dat de tijd gecontroleerd dient te worden en zo verder. Hier wordt het commando geïnitialiseerd zodat eerst tijd en temperatuur gecontroleerd dienen te worden (het commando bevat nu 0x03) vervolgens wordt ook nog de vlag gezet zodat ook het voltage gecontroleerd moet worden (commando bevat nu 0x07). Merk op dat de |= operator dus cummulatief werkt.

#### NOT

Waar AND, OR en XOR binaire operatoren zijn (ze verwachten twee operanden) is NOT een unaire operator, deze verwacht maar één operand. De NOT zal de betekenis van alle bits omwisselen. Met andere woorden 0 wordt 1 en 1 wordt 0.
C-code:
Zal 0x00ff als result geven. Toepassingen van de NOT zijn uiteenlopend, meestal gaat het om afgeleide toepassingen van andere poorten. Indien men, het voorbeeld van de AND indachtig, wil kijken of alle bits hoog zijn behalve een masker gebruikt men hiervoor de NOT. Bij het voorbeeld van de OR kan men zo beslissen om een bepaalde bit NIET te zetten:
C-code:
Hierbij gaat werd de OR vervangen door een AND met een negatie van het bitmasker van dit bits die niet gezet dienen te worden.

#### XOR

XOR (or eXclusieve OR) lijkt op de OR maar is niet zo makkelijk, deze zal een bit enkel hoogplaatsen indien exact één van de bits in de operanden (op overeenstemmende positie) hoog is. In het gekende voorbeeld zal:
C-code:
Een resultaat geven van 0x0ff0.
XOR poorten worden meestal gebruikt in meer gespecialiseerde toepassingen. Bijvoorbeeld bij het berekenen van CRC checksums. Een simpele checksum bestaat bijvoorbeeld uit:
C-code:
Zal een eenvoudige checksum bepalen door een logische XOR te bepalen van alle bytes in een bepaalde buffer. Veel cryptografie gerelateerde functies maken eveneens uitgebreid gebruik van de XOR functie.

#### Shift

Een shift zal een bitmasker een aantal bits opschuiven naar links ( << ) of naar rechts ( >> ). Indien naar links geshift wordt worden rechts nullen ingevoegd en valt het linkerdeel eraf. Een addertje is dat bij het shiften naar rechts niet 0 maar het tekenbit ingevoegd wordt. Het is dus het veiligste te werken op unsigned getallen hier. Shiften over 1 bit naar links is gelijk aan vermenigvuldigen met 2 en is shiften naar rechts (in het unsigned geval) gelijk aan delen door 2. Een voorbeeld:
C-code:
De eerste uitvoer hiervoor is 0xfe00. Binair zit het resultaat er alsvolgt uit:
De tweede uitvoer is 0xf878 welke eveneens logisch is, rekening houdende met het feit dat b unsigned is en het tekenbit (het eerste bit) op één stond en er dus een één (en geen nul) ingeshift werd.
Een shift wordt vaak gebruikt om bits te verplaatsen. Indien men wenst te weten of de 5de (minst significante) bit hoog staat shift men 1 over 5 posities en gebruikt men het voorbeeld uit AND om dit te verifiëren. Gegeven de volgende buffer:
Dus gegeven een buffer van 2 byte (char buf[2]) waarbij men de eerste vier bit een versienummer van 4 dient te geven, vervolgens dient men een teller te splitsen over de volgende 8 bit om te eindigen met een gereserveerd suffix dat 0xf dient te zijn. Uitgewerkt geeft dit:
C-code:
Dit voorbeeld combineert zowel shift, AND en OR. Bij counter worden de bits die niets nodig zijn gemaskeerd en wordt hetgeen resteert geshift zodat het op de juiste positie staat vervolgens ondergaat dit resultaat een OR met een constante.