Appendiks A. Crash course i C sproget

C består som andre programmeringssprog af:

A.1. C i få ord

Programmeringssproget C blev oprindeligt lavet for at kunne skrive styresystemet Unix i et højniveausprog. C er derfor et effektivt sprog, det vil sige, at koden bliver oversat til så få maskininstruktioner som muligt, og derfor kører programmerne hurtigt.

C er også et lille sprog, hvis man måler det i antallet af reserverede ord (kun 32). Det gør det nemt at lære.

C99 standarden tilføjer flg. reserverede ord: inline, restrict, _Bool, _Complex, _Imaginary. Desuden bør man have in mente, at C++ nøgleord også bør undgås, d.v.s. class, private, public, virtual etc.

Anvendt på rette måde bliver koden meget læseligt, og derfor anvendes C sproget til mange forskellige slags opgaver, fra hardware-niveau til højniveauopgaver.

C++ er en udvidelse af C. C++ er tænkt som et bedre C. Det understøtter objektorienteret programmering, dataabstraktion og generiske algoritmer. Der er dog et par forskelle mellem ANSI-C og den grundlæggende C syntaks i C++.

Et minimalt C-program:

Eksempel A-1. Hello world! programmet.


/* Dette program skriver Hello, world! i et terminalvindue. */

main()
{
     printf("Hello, world!\n");
}

Vores program definerer en funktion, d.v.s. et stykke kode, som udfører en opgave. Navnet på vores funktion er main. main er det sted, hvor et C program begynder at køre. Parenteserne efter ordet main fortæller, at det er en funktion.

Koden, det, som bliver til maskininstruktioner, står mellem to krøllede parenteser (eng. braces). Koden består af et kald til en anden funktion, printf, som får til opgave at skrive vores tekst på skærmen. Vores tekst står mellem dobbelt-quotes (gåseøjne, citationstegn). "\n" er måden at skrive et linjeskift, som indgår i en tekst streng. '\' - tegnet, kaldet backslash, signalerer, at nu kommer et bogstav, der skal opfattes på en anden måde. '\n' kaldes derfor et meta-tegn, og backslash anvendes som escape kode.

I kildeteksten kan man skifte linje stort set hvor man har lyst.

Ovenstående kildetekst bør kunne oversættes (compileres) til et kørbart program.


don@pluto $ gcc hallo.c -o hallo 
don@pluto $

-o optionen fortæller compileren, at det færdige program skal skrives til en fil, der skal hedde hallo. Ellers vil compileren lægge det oversatte program i en fil ved navn a.out. Nogle moderne oversættere vil klage over udeladelser. GNU-C vil kun klage, hvis vi anvender kommandolinje option -Wall, som betyder: "Giv mig alle advarsler mod uheldige eller mangelfulde konstruktioner!" En helt korrekt version (også C++) ser ud som følger:

Eksempel A-2. Hello, World! med type-specifikationer.


/* Dette program skriver Hello, world! i et terminalvindue og er
 * meget omhyggeligt med anvendelse af typer. */

#include <stdio.h>

int main()
{
     (void) printf("Hello, world!\n");
     return 0;
}

Eksemplet her erklærer, at main er en funktion, som returnerer en int (integer, heltal). Med sætningen return 0; returnerer den jo så altså netop også et heltal, nemlig 0. return 0; betyder aflever et 0 til den, som satte funktionen i gang. Man siger, at caller får returneret en værdi.

printf er ligeledes en funktion, som returnerer en integer til caller; det er antallet af bogstaver, den har skrevet ud. Da vi ikke har brug for denne integer, fortæller vi oversætteren, at det er "med vilje", at vi ignorer return value ved at skrive (void) . Det kaldes et cast, støbeform: vi omstøber funktionens type!

A.1.1. Data typer

C sprogets indbyggede datatyper er:


int h;        /* integer, heltal, maskinens hurtigste type, native type */

char b;       /* velegnet til bogstaver, men i virkeligheden bare en lille */
              /* integer, 8 bits (eller 9 ...) */

float f;      /* floating point med kun 32 bit, kun til grafikkort o.l. */

double df;    /* 8 eller 10 bytes floating point type */

int ia[10];   /* array med 10 elementer, mange af samme slags */

struct person_type {             /* gruppering af mange typer i en klump */
   int alder;
   char navn[10];
};

struct person_type mig;            /* udlaegning af lager, instantiering */
struct person_type medlem[10];     /* array af struct */

int *j;                            /* adressevariabel, pointer */

En variabel, fx int xyzzy, kan erklæres udenfor en funktion, så er det et eksternt objekt, eller inde mellem krøllede parenteser, braces, så er det en lokal variabel.


/* ramme for eksperimenter med variable af indbyggede typer */

int x;
char b;

main()
{
    double temp = -17.8;

    b = 67;
    printf("x er %d, b er %d, temperatur er %f\n",x,b,temp);
    {
        int *px; /* definition af variabel i top af blok. */
        px = &x; /* tag adressen af x og læg over i p */

        printf("adressen på x er %p\n",px);
    }

    return 0;
}

px er en adressevariabel, d.v.s en variabel, som kan indeholde en adresse. Det kaldes også en pointer. Det ses af, at erklæringen har en stjerne foran px. Man må gerne stille stjernen lige bagefter int, men syntaktisk binder den til px. Derfor kan man fx. erklære en integer pointer og en integer indenfor samme semikolon, int* ptr2tal, tal; (kønt er det ikke! Det bør virkelig undgås, dette her!)

"Og" tegnet, ampersand på engelsk, er adresse-operatoren d.v.s. at den tager adressen på den variabel, den stilles foran. px = &x; betyder tag adressen på x og læg den over i variablen px.

Adresser på en 32-bit CPU ligger mellem 1 og 4 milliarder eller 4 Giga. Altså 232 .

A.1.1.1. Struct - forskellige typer grupperet i en klump

Med struct konstruktionen kan vi selv definere de datatyper, vi har brug for, deraf betegnelse brugerdefinerede typer. Notationen kræver lidt øvelse. Først bygger man en type op, det kaldes, at man erklærer en type . Derefter kan man definere objekter af denne type. Når man definerer et objekt, reserveres der hukommelse i det færdige program til objektet.

Navnet efter ordet struct kaldes en tag (eller type tag; det betyder et mærke eller en mærkat, udtales med g som i væg). Det kan sammen med ordet struct bruges som type-specifikation. I eksemplerne anvendes vinkler (større/mindre tegn, eller på engelsk: angles) til meta ord - der skal indsættes et unikt navn på de pågældende positioner.


struct <type_tag> {
   data-declarations ...
};

struct <type_tag> new_var;

Konkret eksempel:


struct hus_t {
   int grundareal;
   int bebygget_areal;
   int pris;
};

struct hus_t madsensvej_20;

Alternativt kan man opbygge en struct type ved hjælp af typedef. Denne metode foretrækkes af mange, fordi man så ikke behøver at anvende ordet struct når man definerer nye objekter.


typedef struct <type_tag> {
   <data-definitions> ...
} <type-specifier>;           /* <--- her er den nye types navn */

<type-specifier> min_variabel;

Et konkret eksempel på anvendelse af typedef-metoden:


typedef struct {
   int medlems_nr;
   char navn[800];
} medlems_type;                /* <--- her er den nye types navn */

medlems_type medlem[200000];

Somme tider gør ordet struct programmerne lettere at læse. Og ordet struct, gør det lettere for oversætteren at give forståelige fejlmeddelelser.


struct hus_t {                 /* først erklæres ny type */
   char adr[280];
   int pris;
};

struct hus_t mithus;           /* typen anvendes, ram udlægning sker */

main() {
   mithus.pris = 780000;          /* nu kan vi bruge vores variabel */
   strcpy(mithus.adr,"Byvej 20";  /* initialisering af string kræver strcpy */
}

Desuden findes der en nummereringstype, enumeration, som typisk anvendes til en serie symbolske konstanter:

Eksempel A-3. Enumeration


enum farve_t { red, green, blue };

red vil her svare til værdien 0, green til 1 etc. etc... red, green og blue vil indgå som identifiers på linje med andre variable. Man kan nu erklære en variabel af denne type, og en C++ compiler vil kunne kontrollere, at vi ikke tildeler vores variabel ulovlige værdier. En C-compiler overlader dette ansvar til programmøren.

En variant af struct er "union", som svarer til en slags forudbestilt typecasting. Den er som skræddersyet til en program-generator, der skal generere en oversætter - altså en oversætter som oversætter en oversætter. Dette æske system har fra tidligt i 70'erne i unix sammenhæng fået det sjove navn YACC, Yet Another Compiler Compiler. I Gnu tool-settet er det blevet til BISON - fordi bison er en fætter til Yak-oksen. Ja-ja, læs mellem linjerne, at denne her union er kun til specielle lejligheder :-))

Eksempel A-4. Union


union yylval_type {
    long itype;
    tree ttype;
    enum tree_code code;
    const char *filename;
    int lineno;
};
union yylval_type yylval;

A.1.2. Operatorer

Aritmetiske operatorer.


+ plus, addition
- minus, subtraktion
* asterisk, multiplikation
/ stroke ell. slash, division
% percent, modulus, rest af heltalsdivision.

Assignment operatorer, tildeling.


Op.   ex.          Beskrivelse
-------------------------------------------------------------------------------
=     a = b     :  læg værdien af b over i a.

++    ++c       :  læg en til c, increment operator.

                :  ++ operatoren kan stå efter identifieren:
  if (c++ < 42) :  sammenlign med 42 og læg bagefter 1 til c.

--    --c       :  træk 1 fra c, decrement operator.
-------------------------------------------------------------------------------

Desuden kan lighedstegnet kombineres med de forskellige aritmetiske operatorer:


x += kaxi; /* tael x op med vaerdien af variablen kaxi */

Relationelle operatorer, sammenligning.


< mindre end
> større end
<= mindre end eller lig med
>= større end eller lig med

OBS!  ==    test for lighed
      !=    test for not lighed
            Husk det ved at tænke på at 
            den logiske operator NOT er et udråbstegn.
      !     not (tager kun én operand, hvis logiske værdi inverteres)

Et expression er sandt, når det er forskelligt fra 0.

Et expression er falsk, når det er lig med 0.

Eksempler:


  if (a > b) 
      printf("a er for stor\n");

  if (x > 1 || x != y)
      printf("x er større end 1 eller x er forskellig fra y!\n");

Logiske operatorer.


&& logisk AND
|| logisk OR

!  logisk NOT

Bitwise operators, bitvise operatorer, manipulerer de enkelte bit eller bitkolonner efter den almindelige logik.


&    bitvis AND               1 & 1 == 1; 17 & 1 == 1;
|    bitvis OR                1 | 0 == 1; 17 | 1 == 17;
^    bitvis XOR               1 ^ 0 == 1; 17 ^ 1 == 16;
~    bitvis NOT               ~1    == 0xfffffffe;
>>   shift right              1 >>1 == 0; 17 >>1 == 8;
<<   shift left               1 <<1 == 2; 17 <<1 == 34;

Pas på NOT operatoren, det er en tilde. I almindelig netscape opsætning er den meget svær se.

Bitvis NOT er det samme som bitvis invertering. I ovenstående eksempel er resultatet skrevet ud fra en antagelse af, at der er tale om en 32-bit størrelse. Hvis der er tale om 64 bit, så vil der være 15 f'er i stedet for "kun" 7. Hvis 32-bit størrelsen skulle skrives som bit-mønster, så er der selvfølgelig 32 "cifre" der enten er 0 eller 1. Det er en god ting at lave et mellemrum for hver fjerde. Så kan man bedre jævnføre med hexadecimal notation.


~1 == 1111 1111 1111 1111 1111 1111 1111 1110. 

      f    f    f    f    f    f    f    e

      15   15   15   15   15   15   15   14

Så altså, det er lidt nemmere at læse hexadecimal notation:

~1 == 0xffffffe

Der er desværre ikke nogen standard funktion, som udskriver bit-mønster for en integer. Hvis man vil skrive resultatet som BIT-mønster, så må man programmere en funktion, der tester med bitvis and og derefter foretager et shift, fx.


    /* udskriv x's bitmønster: */
    { 
        int i = 0;
        char numstr[35];                    /* 2 + 32 + end of string */
        strcpy(numstr,"b: ");               /* "b:" for binaert format */
        numstr[34] = 0;                     /* End of string mærke */
        while (i++ < 32) {
            numstr[34-i] = (x & 1) + '0';
            x >> 1;
        }
        printf("%s\n",numstr);
    }

Du kan afprøve de ovenstående ved at sætte den ind i en main() funktion, hvor man allerøverst definerer en int x = 0xf0f0f0f0. (File: bitmonst.c).

De andre bitmanipulationer kan også afprøves med små programmer som nedenstående, god øvelse:


main()
{
    printf("17 >> 1 == %d \n",17>>1);
    return 0;
}

Spørgsmålstegns-operatoren

Specielt for C sproget er betingelses-operatoren (conditional operator) spørgsmålstegnet:


  yxi = (a>b)? a: b;

Svarer til:


  if (a > b)
     yxi = a;
  else
     yxi = b;

Andre operatorer

Ud over ovenstående findes der adskillige specielle operatorer, der kun må anvendes efter forudgående aftale med typetjekningssystemet. ;-)


 &   adresse operator, tager adressen af et objekt.

 *   asterisk operator, tag indholdet på den adresse, som specificeres
     efter stjernen, dereferering af adresse. 

 ->  kan bruges ved dereferering af en struct pointer.

 .   Bruges til adressering af et struct element.

 ()  Funktions operator.

 []  Array operator.


 sizeof  (en pseudo funktion, kan bruges som operator, giver os
          størrelsen af det objekt, som den står foran.)

 
 ,   Komma, listeoperator.

Her er et simpelt eksempel - lav selv flere, bare for at afprøve de enkelte operationer!


int charcnt[256];

main()
{
    int yxi;
    double kaxi;
    long double kolme;

    printf("Size of charcnt: %d\n",sizeof charcnt);

    /* sizeof anvendes ofte som en pseudo funktion */
    printf("Size of yxi....: %d\n",sizeof(yxi));

    printf("Size of double.: %d\n",sizeof kaxi);
    printf("long double....: %d\n",sizeof kolme);

}

Eksempel A-5. Operator Præcedens

operatorer niveau associativitet kommentarer
() [] -> . 1 v-h Funktions-parenteser, array-index, struct-element. Dette niveau er det, der binder mest. Grupperings-parenteser er ikke en operator, men anvendes til at ændre rækkefølge af evaluering.
! ~ ++ -- - (cast) * & sizeof 2 h-v not, bit not, increment, fortegns-minus, cast, pointer afreferering, adressen på, objekt-størrelse
* / % 3 v-h multiplikation, division, modulus
+ - 4 v-h addition, subtraktion
<< >> 5 v-h shift
< <= > >= 6 v-h sammenligning, relationsoperatorer
== != 7 v-h relationelle, test for lighed/forskel
& 8 v-h bitvis AND
^ 9 v-h bitvis XOR
| 10 v-h bitvis OR
&& 11 v-h logisk AND
|| 12 v-h logisk OR
?: 13 v-h betingelses-operator
= += -= *= /= %= &= ^= |= <<= >>= 14 h-v tildeling (assignment)
, 15 v-h komma, listeoperator; den mest adskillende operator; dette niveau skiller næsten lige så meget som semikolon


A.1.3. Flow kontrol

Flow kontrol er maskinens måde at reagere på data. Man kan klare sig glimrende med færre, fx. med if, while og goto! Men C sproget er kendt for sine gode flow-konstruktioner.


    if (betingelse_opfyldt)
        do_dyt();

Test inden udførelse:


    while (betingelse_opfyldt)
        do_looping();

Kør loop-body mindst en gang:


    do {
        mindst_en_gang();
    } while (betingelse_opfyldt);

Bemærk at man altid bør bruge braces her for at undgå forveksling med en ordinær while-løkke.

Behagelig kontrol med tæller variable:


    for (initialisering; betingelse; optælling) {
       loop_body ...
    }

fx.


    for (i = 0; i < 10 ; ++i)
        printf("i er nu %d\n", i);

Inde i loops kan man:


    break:      Goto lige efter loop-end.

    continue:   Begynd forfra med test af betingelse.

En switch realiseres om muligt som en jumptabel. En jumptabel er en meget effektiv måde at teste på størrelsen af en int:


    switch (integer_variabel) {
    case 17:
             do_beep();
             break;
    case 42:
             do_hurra();
             break;
    default:
             do_whine();
    }

For at komme ud af en masse loop-kontrol statements:


   goto label;

   /* kode ... */
label:

Bemærk, at case linjerne i switch statementet ligner og opfører sig som labels, de er faktisk labels. Man fortsætter nedefter i næste case, hvis der ikke er et break statement.

Et par eksempler, meget simple, først et eksempel, som smager på værdien af en variabel:


main()
{
    int i,j,k;

    i = 27;
    j = 2;

    if (i > j)
        k = i;
    else
        k = 2;

    return k;
}

Et eksempel på en for-løkke:


main()
    int fahr, celsius;

    for (fahr = 0; fahr < 200; fahr = fahr + 20) {
        celsius = 5 * (fahr - 32) / 9;
        printf("Fahrenheit %3d svarer til celsius %3d\n", fahr, celsius);
    }
}

A.1.4. Modularitet

Funktionsbegrebet i C gør det muligt at genbruge kode; man kan bygge på andres arbejde i stedet for at begynde på bar bund hver gang.

Et C program består typisk af mange forskellige filer, der kan oversættes hver for sig. En fil - evt. med tilhørende header files - der kan oversættes alene til et objekt modul, kaldes en oversættelses-unit eller en translation-unit.

Allerede i vores første program benyttede vi os af, at der i et bibiotek library lå en funktion (printf) som kunne skrive tekst ud på terminalvinduet.

Det er muligt at have private variable i en translation unit.


/* modular programmering - fil nr. 1, kryptio.c */
/* main læser fra tastatur (eller omdirigeret fil)
 * og skriver det krypterede bogstav ud på skærmen.
 */

#include <stdio.h>

main()
{
    int c;
    while ( (c = getchar()) != EOF) {
        c = krypter(c);
        putchar(c);
    }
}

Til ovenstående main vil vi nu skrive et simpelt modul, som foretager kryptering:


int krypter(int inputchar)
{
    return inputchar + 1;   /* 'cæsar' kryptering */
}

Uha, det viser sig snart, at folk gennemskuer vores simple kryptering, så nu laver vi en rigtig kryptering. Det smarte er, at vi kan erstatte dette modul uden at lave om på det eller de programmer, som anvender vores funktion "krypter()".

Bliv nu ikke forskrækket over, at der er en del ting i næste eksempel, som ikke er fyldestgørende forklaret endnu. Prøv at læse det, evt. indtaste og oversætte det. Prøv så at rette i det for at se, hvad der sker undervejs. Når et problem er kompliceret, så skil det ad i mindre dele og indsæt printf statements, så du kan se, hvad der sker undervejs.

Eksempel A-6. Simpel kryptering


/* file krypter1.c - en brugbar (men forsimplet)
 * krypteringsalgoritme. Kan ikke håndtere linjeskift m.v.
 */

int krypter(int inpchar)
{
    static char *keystring = "Under traeerne var der stille og roligt.";
    static int inuse;
    static char *mv;
    static int keylen;

    if (!inuse) {
        inuse = 1;
        mv = keystring;
        keylen = strlen(keystring);
    }
    if (mv - keystring > keylen)
        mv = keystring;
    if (inpchar < ' ')
        inpchar = ' ';
    else if (inpchar > 126)
        inpchar = '~';
    return (inpchar + *mv++) % 93 + 33;
}

Virker kun for ren ASCII tekst. [1]

Ordet static, som står foran de 4 variable, som skal anvendes i modulet her, betyder, at de ikke må kunne bruges fra andre moduler eller funktioner i det færdige program. Det er såmænd ikke fordi de skal være hemmelige, men blot for at sikre, at vi har styr på, hvor der sker ændringer af variablen, som peger fremad i krypteringsnøglen.

Hvis du blev bidt af ovenstående, så lav en dekryptering til den. Eller læs eksempel-programmet afkrypt1.c. Desuden er der en forbedret version, som håndterer linjeskift, krypter2.c og afkrypt2.c

Det var små eksempler på anvendelse af datatyper, operatorer, flowkontrol statements og modularisering. Det næste afsnit af crash course i C programmering går lidt mere i dybden med disse emner.

Først et spørgsmål: Lagde du mærke til, at variablen 'keylen' havde fået ordet static stillet foran? Det er en storage specifikation. Læs videre!

Slutbemærkning:

[1]

I parentes bemærket, hvis du vil gøre krypteringen ovenfor rigtig ubrydelig skal du forbedre den lidt. Anvend længere keystring (altid længere end meddelelsen), formatér output i linjer, som er lige lange. Bemærk, at hvis input består af lutter samme tegn, så kan denne version ikke rigtigt skjule sin keystring. Ikke at den kommer i klartekst, men det er muligt at regne baglæns og finde keystringen. Man skal behandle sekvenser af samme tegn på en speciel måde, hvor man angiver antal og tegn. Algoritmen er en tilpasning af Kejser Augustus' og Livia's kode, som beskrevet i Robert Graves' "I, Claudius".

Endelig er der ikke support for såkaldte offentlige keys, der kan bruges til afkodning men ikke indkodning.