10. Lektion

Denne lektion omhandler maskinnær programmering

I de forrige lektioner har vi brugt biblioteksfunktioner til meget af det arbejde, der er udført med hardwaren. En af styrkerne i C er netop, at man kan programmere direkte til hardwaren. C har en del funktioner, som kan arbejde på bit-niveau, hvilket netop er nødvendig, når man vil arbejde direkte på isenkrammet.

Der findes forskellige grunde til, at man vil arbejde direkte med hardwaren. Det kan for eksempel skyldes, at man ønsker, at et program skal reagere hurtigere, end det måske er muligt med normale højniveausprog. En anden grund kan være, at man ønsker at udarbejde programmer, som fylder så lidt som muligt i den hukommelse, der er til rådighed.

Ved at lære de teknikker, som anvendes ved maskinnær programmering, får man også udvidet sin viden omkring generel programmering ganske væsentligt.

Denne lektion er ikke udtømmende med hensyn til maskinnær programmering, men den vil hjælpe godt på vej. Hvis man vil vide mere om maskinnær programmering, kan det anbefales at man anskaffer sig speciallitteratur om emnet, eller deltage i et specialkursus.

Lektionen vil beskæftige sig med bit-manipulering. Det grafiske display er noget helt for sig selv, som det kan anbefales at skaffe sig viden om, hvis man skal udføre egentlig skærmarbejde. Skærmarbejde er meget tæt knyttet til de grafikkort, som understøtter skærmene. Programmering af drivere til skærmen er således et helt studie for sig selv.

Bit-manipulering
Indtil nu har vi kun betragtet variabler, som "skuffer" vi puttede tal eller bogstaver (tegn) ned i. Vi har ikke tænkt videre over, hvorledes de enkelte variabler er bygget op af bits, og vi har slet ikke overvejet, om vi måske kunne få noget ud af at manipulere de enkelte bits i en variabel. Men at være i stand til at manipulere bits er vigtigt, hvis man virkelig skal have noget ud af at håndtere hardwaren direkte. Normal programmering er almindeligvis data-orienteret, mens hardware-programmering er bit-orienteret. Det betyder, at hardware enhederne ofte kræver, at input og output sker i form af individuelle bits.

Bit-operatoren AND (&)
Som vi har set tidligere, består en C-variabel af bytes, og hver byte består af 8 bit. En bit kan kun have værdien 0 eller 1 (slukket eller tændt). En bit-operator arbejder med normale C heltal og tegn, men ikke med reelle tal. Bit-operatorerne behandler indholdet af en variabel, som om variablen består af individuelle bits i stedet for som ét tegn eller ét tal. Det findes seks bit-operatorer, det er:

Operation                 Operator

AND                               &
Inclusive OR                    |
Exclusive OR (XOR)        ˆ
Højre flyt                         >>
Venstre flyt                      <<
Complementær                 ˜

Fordi operatorerne har indflydelse på de enkelte bits, er det vigtigt at vide, hvorledes de har effekt på bits i et tegn eller et heltal. Bits bliver nummereret fra højre mod venstre. Se figuren herunder.

Vi starter med at se på bit-operatoren AND. Operatoren er repræsenteret ved tegnet &. Husk, at det var &&, der blev anvendt ved det logiske AND, som vi brugte til sammenligning af variabler i tidligere lektioner.

Bit-operatoren & arbejder på to argumenter, disse skal være af samme data-type. Lad os se, hvordan det virker, vi er på bit-niveau, lad os derfor se på bit nummer 0 (nul). Hvis bit'en er 1 i begge argumenter bliver resultatet 1, hvis den ene er 1 og den anden 0, bliver resultatet 0, hvis begge bits er 0 bliver resultatet også 0.

For AND, OR og XOR gælder at reglen anvendes på hver enkelt bit på det pågældende argument, bit 0 sammenlignes med bit 0, bit 1 sammenlignes med bit 1 osv. I modsætning til almindelige matematiske operationer er operationerne på bit-niveau, fuldstændig uafhængig af hinanden, når man betragter et tegn bit for bit. Det "bæres" således ingen informationer fra en bit til en anden inden for det samme argument.

Se figuren herunder, hvorledes AND fungerer.

Eksempel 1
void main(void)
{
   unsigned int x1=0xFF, x2=0xFF; 

   while( x1 != 0 || x2 != 0 ) 
   {
      printf("\nIndtast to hexadecimaltal (ff eller mindre): ");
      scanf("%x %x", &x1, &x2); 
      printf("%02x & %02x = %02x\n", x1, x2, x1 & x2 );
   }
}

Bit-operatoren AND anvendes ofte til at teste om en speciel bit i et argument er sat til 0 eller 1. Testen virker fordi 0 AND'et med 0 eller 1 giver 0. Mens 1 AND'et med en anden bit altid giver det resultat, som bit'en har.

Bit-operatoren OR ( | )
En anden vigtig bit-operator er OR, som repræsenteres ved en lodret streg ( | ). Hvis to bits bliver sammenlignet med OR, fås resultatet 1, hvis en af argumenterne er 1 eller begge er 1, hvis begge er 0 fås et 0.

Figuren herunder viser, hvorledes OR virker.

Eksempel 2
void main(void)
{
   unsigned int x1=0xFF, x2=0xFF; 

   while( x1 != 0 || x2 != 0 ) 
   {
      printf("\nIndtast to hexadecimaltal (ff eller mindre): ");
      scanf("%x %x", &x1, &x2);
      printf("%02x | %02x = %02x\n", x1, x2, x1 | x2 );
   }
}

Bit-operatoren OR bliver ofte brugt til at kombinere bits fra forskellige argumenter til et enkelt argument, med de ønskede bit sat. Hvis man har to bytes, hvor man ønsker at anvende de fire første bit fra den ene og de fire sidste fra en anden, og disse otte bit skal samles til en ny byte, med de ønskede bit sat. Hvis de uønskede bits er sat til 0, kan den nye byte dannes med:

svar = ch1 | ch2;

Bit-operatoren Højre flyt ( >> )
Ud over operatorer, som arbejder med to argumenter, er der også operatorer, som kun arbejder på en enkelt variabel. Højre flyt operatoren er et eksempel på sådan en operator. Operatoren er repræsenteret ved >>. Operatoren flytter hver bit det antal pladser, som en angivet ved tallet, som følger efter operatoren. F.eks.:

ch >> 3;

medfører, at alle bits i ch flyttes tre pladser mod højre. Bits i højre ende af byten forsvinder, mens der i venstre ende fyldes op med 0'er eller 1'er i de bits, som bliver tømt. Hvis det er unsigned char er det 0, mens det ved char kan være 1. Bemærk at en flytning en plads mod højre er det samme som at dividere byteværdien med 2.

Reglen er: Hvis en fortegnsbit er sat i en signed operand, vil Højre flyt resultere i, at der indsættes 1 i venstre side af variablen ellers 0.

Eksempel 3
void main(void)
{
   unsigned int x=0xFF, bits;

   while( x != 0 )
   {
      printf("\nIndtast et hexadecimaltal (ff eller");
      printf(" mindre) og det antal bits ");
      printf("\nsom skal flyttes (8 eller mindre;");
      printf(" eksempel: 'cc 3'): ");
      scanf("%x %d", &x, &bits);
      printf("%02x >> %d = %02x\n", x, bits, x >> bits );
   }   
}

Prøv at arbejde med følgende test i programmet:
80 >> 2 = 20
80 >> 7 = 01
f0 >> 4 = 0f

Værdierne er hex, hvis det er svært at se, at beregning er korrekt, så prøv at omskrive værdierne til binære værdier og udfør højreflytningen manuelt.

Hexadecimal til Binær konversion
I afsnittet herover skulle der udføres en konvertering fra hex til binær for at kontrollere beregningen. Det er muligt ved hjælp af %x at konverter fra decimal til hexadecimal, men der findes ingen funktioner indbygget i C, som kan konvertere hexadecimal til binær, vi vil derfor prøve at lave sådan et program herunder.

Eksempel 4
void main(void)
{
   int j, num=0xFF, bit;
   unsigned int mask;

   while(num != 0)
   {
      mask = 0x8000; // 1000000 binær
      printf("\nIndtast et hexadecimaltal: ");
      scanf("%x", &num);
      printf("\nBinærværdien af %04x er: ", num);
      for(j=0; j<16; j++) // for hver bit
      {
         bit = (mask & num) ? 1 : 0;
         printf("%d ", bit);
         if(j==7)
            printf("-- ");
         mask >>= 1;
      }
   }
}

Dette program anvender en for-løkke til at gå fra venstre til højre igennem alle 16 bits, som indeholdes i en heltalsvariabel. Hjertet af programmet er to sætninger, som arbejder med bit-operatorer. Det er:

bit = (mask & num) ? 1 : 0;
og
mask >>= 1;
Denne sætning kunne også skrives mask = mask >> 1;

I den første sætning anvender vi en maske (variablen mask). Dette er en variabel, som starter med at indeholde den bit, som står yderst til venstre. mask er AND'ed med det tal (variablen num), der ønskes udtrykt binært. Hvis resultatet er forskelligt fra nul, ved vi, at den tilsvarende bit i num er 1, i alle andre tilfælde er værdien 0. Den conditional-sætning placerer 1 i variablen bit, hvis den bit, der testes på i num, er1 ellers tildeles værdien 0.

Bit-operatoren XOR ( ^ )
Ved OR blev resultatet 1, hvis enten en af bit-værdierne var 1, eller hvis de begge var 1. Med XOR gælder, at hvis enten den ene eller den anden er1, fås 1, hvis de to bit er ens, dvs. begge er 1 eller begge er 0, fås 0. Se figuren herunder.

XOR-operatoren er meget nyttig, hvis man ønsker at fange en bit, som skifter mellem 1 og 0. Dette skyldes, at hvis man anvender XOR på to bits, hvor den ene holdes til 0, vil man hele tiden få at vide, hvad den anden er blevet til.

Bit-operatoren Venstre flyt ( << )
Venstre flyt operatoren, som man måske kan gætte, virker på samme måde som Højre flyt, blot flyttes de enkelte bit mod venstre. I dette tilfælde er det dog altid et 0, som indsættes i den højre ende af argumentet. Det gælder uanset om argumentet er en char eller unsigned.

Bit-operatoren Complementær ( ~ )
Bit-operatoren complementær er repræsenteret ved ~, den arbejder kun på et argument. Den tager hver bit i argumentet og ændre et 1 til et 0 og et 0 til et 1. Se det følgende eksempler.

~03    ==  fc
~ffff  ==   0
~cc    ==  33
~88    ==  77

Hvis man complementerer en værdi to gange, skal det altid føres tilbage til den første værdi.

En lille bit-regnemaskine
I det følgende program er alle operationerne fra eksemplerne 1, 2 og 3, samlet i en regnemaskine, endvidere er de øvrige tre bit-operatorer også indbygget.

Eksempel 5
// pbin
// printer tal både i hex og binært

void pbin(int num)
{
   unsigned int mask;
   int j, bit;

   mask = 0x8000;
// en-bit masken
   printf("%04x ", num);
// print i hex
   for(j=0; j<16; j++)
// for hver bit i num
   {
      bit = (mask & num) ? 1 : 0;
// bit er 1 eller 0
      printf("%d ", bit);
// print bit
      if(j==7)
// print streger
      printf("-- ");
// imellem bytes
      mask >>= 1;
// flyt mask til højre
   }
   printf("\n");
}
// slut på pbin()

// Delelinie()
void dline(void)
{
   printf("----------------------------------------\n");
}

void main(void)
{
   char op[10];
// operator som en streng
   unsigned int x1=0xFF, x2=0xFF;

   while( x1 != 0 || x2 != 0 )
// afslut når 0 eller 0
   {
      printf("\n\nIndtast udtryk (eksampel 'ff00 & 1111'): ");
      scanf("%x %s %x", &x1, op, &x2);
      printf("\n");
      switch( op[0] )
      {
         case '&':
            pbin(x1); printf("& (and)\n"); pbin(x2);
            dline(); pbin(x1 & x2);
            break;
         case '|':
            pbin(x1); printf("| (incl. or)\n"); pbin(x2);
            dline(); pbin(x1 | x2);
            break;
         case '^':
            pbin(x1); printf("^ (xor)\n"); pbin(x2);
            dline(); pbin(x1 ^ x2);
            break;
         case '>':
            pbin(x1); printf(">> "); printf("%d\n", x2);
            dline(); pbin(x1 >> x2);
            break;
         case '<':
            pbin(x1); printf("<< "); printf("%d\n", x2);
            dline(); pbin(x1 << x2);
            break;
         case '~':
            pbin(x1); printf("~ (complementær)\n");
            dline(); pbin(~x1);
            break;
         default: printf("Ikke en gyldig operator.\n");
      }
// slut på switch
   }
// slut på while
}
// slut main()

Som man kan se består programmet stort set kun af en switch-konstruktion, som indeholder alle bit-operatorerne. Brugeren skal således indtaste et argument efterfulgt af en operator og sluttelig det andet argument. For at gøre indtastningen universel skal der indtastes 2 operatorer til complementær-funktionen, men det er kun den første af argumenterne, der anvendes ved beregningen.

Øvelse 1
Find ud af hvilket resultat man får af 0c & 07, når 0c og 07 er hex-værdier?

Eksempel 1
Eksempel 2
Eksempel 3
Eksempel 4
Eksempel 5