Programmeren in C/Datatypes

Uit Wikibooks

Programmeren in C

Inleiding
  1. Inleiding Redelijk ontwikkeld. Revisiedatum: 23 oktober 2007
  2. De compiler Redelijk ontwikkeld. Revisiedatum: 23 oktober 2007

Bewerkingen

  1. Basis Goed ontwikkeld. Revisiedatum: 23 oktober 2007
  2. Stijl en structuur Redelijk ontwikkeld. Revisiedatum: 23 oktober 2007
  3. Datatypes Goed ontwikkeld. Revisiedatum: 11 november 2007
  4. Berekeningen In ontwikkeling. Revisiedatum: 23 oktober 2007
  5. If en loops Goed ontwikkeld. Revisiedatum: 23 oktober 2007
  6. Arrays en pointers Redelijk ontwikkeld. Revisiedatum: 24 oktober 2007
  7. Functies Goed ontwikkeld. Revisiedatum: 23 oktober 2007
  8. File handling In ontwikkeling. Revisiedatum: 23 oktober 2007

Overige

  1. Bestanden Redelijk ontwikkeld. Revisiedatum: 23 oktober 2007
  2. C-Preprocessor (CPP) Redelijk ontwikkeld. Revisiedatum: 23 oktober 2007
  3. Struct
  4. Expressies

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[bewerken]

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.

officiële naam afgekorte naam aantal bits typische implementaties
8/16 bits 32 bits 64 bits
short int short 16 16 16 16
int int minimaal 16 16 32 32
long int long minimaal 32 32 32 64
long long int (sinds C99) long long minimaal 64 meestal afwezig 64 128

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.

officiële naam afgekorte naam aantal bits typische implementaties
8/16 bits 32 bits 64 bits
unsigned short int unsigned short 16 16 16 16
unsigned int unsigned minimaal 16 16 32 32
unsigned long int unsigned long minimaal 32 32 32 64
long long int (sinds C99) unsigned long long minimaal 64 meestal afwezig 64 128

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[bewerken]

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.

type betekenis
char een karakter, met of zonder teken, al naar gelang de compiler
signed char een karakter met teken-bit
unsigned char een karakter zonder tekenbit

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[bewerken]

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

Daarin zijn:

Element symbool Betekenis
sign s het teken (1 of -1)
radix r de wortel, het grondgetal
exponent e de exponent van de wortel
mantissa m een niet-negatief getal dat de waarde weergeeft.

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 ). 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:

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:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
  double x = 1.0/3;
 
  if( x*3 == 1.0)
  {
    printf("Ok...\n");
  }
  else
  {
    printf("Mooi niet! Lekker PUH!\n");
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

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:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define EPSILON (1e-10)

int main(void)
{
  double x = 1.0/3;
 
  if( fabs(x*3 - 1.0) < EPSILON)
  {
    printf("Ok...\n");
  }
  else
  {
    printf("Mooi niet! Lekker PUH!\n");
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

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[bewerken]

Alle types getallen hebben een signed en een unsigned variant. Dit sleutelwoord wordt voor de variabele geplaatst. Bijvoorbeeld:

C-code:

unsigned int i;

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:

#include <stdio.h>
int main(){
    printf("char        :%d\n", sizeof(char));
    printf("short       :%d\n", sizeof(short));
    printf("int         :%d\n", sizeof(int));
    printf("long        :%d\n", sizeof(long));
    printf("long long   :%d\n", sizeof(long long));
    printf("float       :%d\n", sizeof(float));
    printf("double      :%d\n", sizeof(double));
    printf("void *      :%d\n", sizeof(void *));
    return 0;
}

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):

char        :1
short       :2
int         :4
long        :4
long long   :8
float       :4
double      :8
void *      :4

enum[bewerken]

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:

enum kleur
{
  Rood,
  Groen,
  Blauw
};

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:

enum kleur
{
  Rood = 0, /* Rood heeft nu een numerieke waarde '0' */
  Groen,    /* Groen heeft nu een numerieke waarde '1' */
  Blauw     /* Blauw heeft nu een numerieke waarde '2' */
};

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:

enum kleur
{
  Rood = 0,
  Groen,
  Blauw
};

enum kleur k; /* een variabele van het type ''enum kleur" */
int        i; 

k = Rood;  /* Ok */
i = Rood;  /* Ok, de enum wordt "gepromoveerd" naar een int */
k = 21231; /* FOUT, hoewel de compiler er geen error op zal genereren. */

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[bewerken]

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:

/* Dit is een functie die geen argumenten heeft en geen waarden teruggeeft. */
void foobar(void)
{
  printf("Helemaal NIETS\n");
}

bool[bewerken]

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[bewerken]

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:

void foobar(void)
{
  int i;     /* een integer */
  int *ip;   /* een pointer naar een integer */
  int **ipp; /* een pointer naar een pointer naar een integer */

  float f;   /* een float */
  float *fp; /* een pointer naar float */

  char c;
  char* cp;  /* een pointer naar een char */

  void* vp;  /* een pointer naar een iets (wat? geen idee...). */

  void (*funp)(void); /* een pointer naar een functie die
                       * niets teruggeeft en geen argumenten
                       * verwacht
                       */
  funp = foobar; /* funp bevat nu een pointer naar *deze* functie! (foobar) */
}

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.


TE DOEN
TE DOEN

TE DOEN
Hier zou een voorbeeld niet misstaan. Waarom worden pointers gebruikt en in welke situaties

Operatoren[bewerken]

Overzicht[bewerken]

TE DOEN
TE DOEN

TE DOEN
Een tabel zou handig zijn

Bitgerichte operatoren[bewerken]

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[bewerken]

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:

#include <stdio.h>
int main(int argc, char **argv){
    unsigned short a = 0xff00;
    unsigned short b = 0xf0f0;
    unsigned short c = a & b;
    printf("0x%x\n",c);
    return 0;
}

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:

#define DATA_ONTVANGEN 0x2
...
unsigned char status;
...
if(status & DATA_ONTVANGEN == DATA_ONTVANGEN){
...
}

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[bewerken]

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:

#include <stdio.h>
int main(int argc, char **argv){
    unsigned short a = 0xff00;
    unsigned short b = 0xf0f0;
    unsigned short c = a | b;
    printf("0x%x\n",c);
    return 0;
}

Een resultaat geven van 0xfff0.

Een OR wordt het meest gebruikt om ervoor te zorgen dat bepaalde bits aanstaan, bijvoorbeeld:

C-code:

#define VLAG_CONTROLEER_TEMP 0x1
#define VLAG_CONTROLEER_TIJD 0x2
#define VLAG_CONTROLEER_VOLT 0x4

...
unsigned char commando=0;
...
commando = VLAG_CONTROLEER_TEMP | VLAG_CONTROLEER_TIJD; 
...
commando |= VLAG_CONTROLEER_VOLT;
...

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[bewerken]

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:

#include <stdio.h>
int main(int argc, char **argv){
    unsigned short a = 0xff00;
    unsigned short c = !a;
    printf("0x%x\n",c);
    return 0;
}

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:

#define VLAG_CONTROLEER_TIJD 0x2
#define VLAG_CONTROLEER_VOLT 0x4

...
unsigned char commando=0;
...
commando = VLAG_CONTROLEER_TEMP & !VLAG_CONTROLEER_TIJD; 
...
commando |= VLAG_CONTROLEER_VOLT;
...

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[bewerken]

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:

#include <stdio.h>
int main(int argc, char **argv){
    unsigned short a = 0xff00;
    unsigned short b = 0xf0f0;
    unsigned short c = a ^ b;
    printf("0x%x\n",c);
    return 0;
}

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:

#define SIZE 20
...
unsigned char myData[SIZE];
...
unsigned char checksum=0;
...
for(int i=0;i<SIZE;i++){
    checksum ^= myData[i];
}

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[bewerken]

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:

#include <stdio.h>
int main(int argc, char **argv){
    unsigned short a = 0xff00;
    signed short b = 0xf0f0;
    unsigned short c = a ^ b;
    printf("0x%x\n",c);
    c = b>>1
    printf("0x%x\n",c);
    return 0;
}

De eerste uitvoer hiervoor is 0xfe00. Binair zit het resultaat er alsvolgt uit:

   a:   1111111100000000
   a<<1:1111111000000000

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.

   b:   1111000011110000
   b>>1:1111100001111000 

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:

 +---------------------------------+
 |versie: 4 bit | teller 4 msb bit |
 |teller 4 lsb  | reserved 0xf     |
 +---------------------------------+

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:

#define VERSION 0x4
#define RESERVED 0xf
...
unsigned char buf[2];
...
unsigned char counter=0;
...
buf[0] = (VERSION << 4) | ((counter & 0xf0) >> 4);
buf[1] = ((counter & 0x0f) << 4) | RESERVED;

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.

Informatie afkomstig van https://nl.wikibooks.org Wikibooks NL.
Wikibooks NL is onderdeel van de wikimediafoundation.