A.2. Lager klassifikation, storage classes

Emner:

Et C program består af definitioner af eksterne objekter, som kan være funktioner eller variable. Eksterne objekter kan bruges overalt i det program, hvori de forekommer, også fra separat oversatte funktioner.

Modsætningen til eksterne variable er de variable, som defineres inde i en funktion og de parametre, som funktionen modtager når den bliver kaldt. De eksisterer, mens funktionen kører. De bliver automatisk oprettet når programmet kører funktionen (eller blokken) og nedlægges igen, når programmet forlader funktionen. De kaldes derfor også automatiske variable. Det reserverede ord auto kan anvendes om lokale variable, men er overflødigt - det stammer fra de tidligste versioner af C sproget.

Funktioner er altid eksterne objekter; man kan ikke definere en funktion inden i en anden funktion, sådan som man for eksempel kan i Pascal sproget.

Selv om eksterne objekter uden videre er "synlige" i hele programmet, er der en metode til gøre dem private for et modul. Det skal vi se på senere i dette afsnit.

A.2.1. Externe variable, funktioner og konstanter

For helt at være klar til diskussion af eksterne variable skal det lige understreges, at et C program næsten altid opbygges af separat oversatte moduler. De fleste af dem ligger måske i et library - måske er det standard-library, libc.a - Andre ligger i vores egne oversatte moduler, og vi kan give oversætteren besked om det på kommandolinjen ved simpelthen at nævne alle de moduler, som skal med. Oversætteren består af mange forskellige programmer, og det sidste af dem, nemlig linkeren, foretager sammenkædningen af modulerne (link = hægte eller led i en kæde).

gcc -o mitprogram modul_1.c modul_2.c modul_3.o

Oversætteren finder ud af, at modul_3.o er et oversat modul og nøjes med at medtage det i link-kommandoen, som bygger den kørbare fil, "program-image filen" eller bare "programmet".

En definition af en (ekstern) variabel består af en type angivelse og et navn, kaldet en variabel-identifikation, på engelsk: identifier.

typespecifikator variabel-navn [, variabel-navn] ... ;

Altså typisk for eksempel følgende:

int en_taeller;

En funktion defineres nogenlunde på samme måde, altså ved en type og et navn (lige som en variabel) men derefter har vi brug for nogle flere oplysninger, nemlig en parentes med argumentlisten, og de program-sætninger (statements), som udgør funktionens krop, omsluttet af braces.

typespecifikator funktion-navn(parameter-liste)
{
    funktion-sætning(er);
}

En minimum funktion ses nedenfor:

int placeholder() { }

Selv om en tom funktion ikke laver noget, kan den dog være ganske nyttig under udviklingen af et større program ved simpelthen at være til stede, evt. returnere en brugbar (default) værdi.

Der skelnes i C sproget mellem en definition og en deklaration. På dansk ser man ofte ordet erklæring i stedet for deklaration.

Hvis vi ønsker at anvende en variabel, som allerede er defineret i et andet modul, så bruger vi en deklaration. Det er en forklaring, så at sige, til oversætteren om, at der er en variabel til rådighed i et andet modul i det færdige program, som hedder sådan og sådan og er af den og den type, men som altså IKKE findes i nærværende overstættelses-enhed (translation unit).

extern int en_taeller;

Nu ved oversætteren, at der i et andet modul findes en variabel, som hedder en_taeller, og at det er ok, at vi bruger den.

Hvis vi derimod vil reservere lager til en variabel, så kaldes det definition af en variabel. Når programmet kører og vi opretter et objekt (enten ved hjælp af dynamisk lagerallokering eller ved hjælp af lokale variable i en kodeblok) kaldes det instantiering af et objekt.

I C foregår deklaration og definition ofte på samme tid. Når jeg skriver int xyzzy foretager jeg både deklaration og lagerudlægning.

Hvis jeg derimod skriver extern int xyzzy udlægges der ikke lager, men jeg indføjer et punkt i oversætterens symboltabel. I denne entry ligger oplysning om navn og type.

I det andet modul skal der være defineret en variabel på det yderste niveau - så er det en ekstern variabel, som kan eksistere uafhængigt af de funktioner, som det andet modul måtte indeholde (måske indeholder det kun variable). En variabel, som er erklæret i det yderste niveau er altid en variabel, som kan ses af andre moduler. Den er, pr default, eksporteret, ville man måske sige i sprog som ADA og Oberon.

En ekstern variabel er - i C sproget - også altid en statisk variabel. Sådanne statiske variable har lige så lang levetid som det program, der anvender dem. På den måde ligner de programmets instruktioner.

Programkode og statiske data ligger da også anbragt på samme måde i lageret, nemlig forneden, på de lave adresser, under det område, som anvendes til hhv. bunke (heap) og stak. Både heap og stak kan vokse, så det er meget passende, at de enes om at udnytte det store mellemrum fra "bunden" med faste data og programinstruktioner og "toppen" hvor operativsystemet forlanger at få lov til at være i fred.

toppen af lageret
Operativ system kode
Stak for bruger-applikationen
Mellemrum mellem stak og heap
Heap til dynamisk lageradministration
.bss, nulstillede externe variable
.data initialiserede, eksterne variable
.string konstanter mv.
.text, program instruktioner

Der er dog lidt forskel på, hvor data anbringes, er de initialiserede, således som for eksempel vores keystring i eksempel Eksempel A-6, anbringes de i en sektion, som kaldes .data. Hvis vi ikke selv specificerer, at data skal initialiseres, anbringes variablen i den sektion, som kaldes BSS (eller .bss), og alle bit-terne vil blive nulstillet.

Navnet bss har rødder i IBM mainframe assembler sproget, d.v.s. system 370, 390, 3900 og MVS. Det står for Block Started by Symbol.

Lad os i stedet huske det som Base Static Storage. Det er garanteret, at variable, som erklæres uden for en funktionsblok men som ikke er explicit initialiserede ved en "initializer", er nulstillede ved programmets start. Det sørger opstartsmodulet af programkoden for, traditionelt kaldet c0.c

Læg især mærke til, at lokale variable anbringes på "stak for applikationen". Det er de højest tilgængelige adresser for vores program. Herfra kan stakken vokse (hov - den vokser nedad!) indtil den møder heap. På en linux-maskine kan den vokse mere end to gigabyte, før der sker sammenstød. MS NT har "kun" afsat 2 Gb til applikationer.

A.2.2. Storage specifications

Det reserverede ord static betyder selvfølgelig statisk. Det kan, som vi har set i Eksempel A-6, bruges, hvis man inde i en funktion ønsker at have en variabel, som overlever funktionen, så at sige, husker fra gang til gang, hvad værdi, den havde sidste gang. For eksempel en sidetæller i en udskriftsfunktion.

void topmargin_udskrift(char *section_navn)
{
     static int sidenummer;
     printf("%s %d\n", section_navn, sidenummer);
}

Stakken gør det muligt at have lokale variable i funktioner, og for at have et sæt variable HVER gang funktionen kaldes, det er betingelsen for, at man kan benytte rekursion. Tilsvarende er heap'en det område, som benyttes til dynamiske lager-strukturer som for eksempel en "liste" og et "træ". (se kap. 3, Afsnit 3.3).

De steder, hvor der i nedenstående opstilling af lager-anvendelse er anbragt nogle punktummer på sidelinjen er steder, hvortil der ikke nødvendigvis er knyttet noget RAM, fysisk memory. Programmets anbringelse i lageret ser normalt ud sådan her:

              +----------------------------+
              | Den øverste gigabyte       |
              |   som operativsystemet     |
              |     benytter og beskytter  |
              |                            |
              .                            .
              .                            .
              .                            .
              |                            |
              |                            |
              +----------------------------+
              |                            |
              |  Stack område for          |
              |          brugerprogram     |
              |                            |
              |  Her laves en ramme med    |
              |  data for hver function    |
              |  som kaldes                |
              |                            |
              .                            .
              .                            .
              .                            .
              .                            .
              |                            |
              +----------------------------+
              |                            |
              |  Heap - der, hvor man kan  |
              |    allokere mere lager     |
              |                            |
              |  Benyttes af funktionerne  |
              |    malloc(3), calloc(3),   |
              |     free(3) og realloc(3). |
              |                            |
              |                            |
              +----------------------------+  -
              |                            |   \
              |  bss -                     |    \
              |    blok started by symbol  |     \
              |  ell. base static storage  |      NULSTILLES
              |                            |      ved load af program
              |  static variable som IKKE  |     /
              |  er initialiserede         |    /
              |                            |   /
              +----------------------------+  -
              |  .data                     |
              |  initialiserede strings mv.|
              +----------------------------+
              |  .text                     |
              |  Programmets instruktioner |
              |  d.v.s. kommandoer til CPU |
              +----------------------------+
              | Lille stykke i bunden til  |
              |   Operativsystemets        |
              |   Hardwarehaandtering      |
              +----------------------------+

Hvis vi ønsker at anbringe en variabel i .bss sektionen kan vi enten erklære den uden for en funktion eller benytte lagrings-specifikationen static. Sidstnævnte metode muliggør statiske variable med lokal synlighed (altså kun synlige inde i en funktion eller blok). Hvis vi benytter "static" på eksternt niveau, d.v.s. uden for en funktionsdefinition, har specifikationen den virkning, at variabelen ikke får ekstern synlighed (external linkage).

Hvis en variabel erklæret inde i en funktion skal kunne huske sin værdi fra gang til gang, når funktionen kaldes, skal den defineres som static, hvilket vil bevirke, at den bliver anbragt i .bss sektionen.

A.2.3. Konstanter og memory protection

Der er forskellige måder at organisere kørselen af et program, men stort set alle benytter ovenstående opstilling som udgangspunkt. Det egner sig til virtual memory management, hvilket jo fra starten var udgangspunktet for Linux, og det giver nogle fordele, som er ret store. Vi kan simpelt hen lade vores program benytte 3 Gb memory - også selv om vi ikke har installeret så meget. Afhængig af operativsystemets snedighed vil der kun blive tildelt fysisk memory, når det er strengt nødvendigt. Det vil sige, hvis vi vil skrive til memory. Når vi har loadet vores program og vil starte det op igen fra en anden brugers terminal, kan man stole på, at operativsystem kernen ikke bruger mere RAM til at loade endnu en kopi af program instruktionerne.

Så opdelingen af programmet i sektioner skyldes både vores egne behov for at strukturere programmet og så et hensyn til operativsystemet. Sektionerne skal gøre det lettere for operativsystemet at optimere anvendelsen af memory. Nogle sektioner skal kun læses, for eksempel program-instruktionerne, andre skal både læses og opdateres, og i hhv. heap og stack skal man kunne udvide området under kørselen.

Hvis nu vores program alligevel forsøger at skrive på fx. adressen for en funktion, så skal Linux kernens memory management modul reagere ved at stoppe programmet.

På samme måde er der dele af initialiserede data, som ikke må overskrives. Fx. streng-konstanten "Hello, world!\n" i eks. Eksempel A-1. Man kan heller ikke så nemt komme til at gøre det i netop det eksempel. Men se lige her:

Eksempel A-7. Konstant streng

/* constviol.c, kørselsfejl når man ændrer en konstant. */
#include <stdio.h>

char * ms1 = "Hello, ever changing world!\n";

int main()
{
     *ms1 = 'A'; /* segmentation violation, signal 11 */
     return 0;   /* abort forinden, exitkoden er 139 == 128+11 */
}

A.2.4. Storage specifikation og scope

Scope betyder synlighed (within scope = "inden for synsvidde"). En af de rigtig gode ting ved C sproget er, at man kan skrive programmerne således, at variable kun er tilgængelige i en funktion eller et modul. Derved kan man uden stort besvær anvende objektorienterede programmeringsprincipper.

Eksempel A-8. Scope eller synlighed

/* ... demonstration af scope regler, synligheds regler. */

char * ms1 = "Min nye Hello-world rapport";
char * ms2 = "(c) 2001 Donald Axel, 3210 Vejby";
static char *msp = "Denne char-ptr kan kun bruges i denne fil."

headerlinje(){
     static int sidenummer; /* kan kun bruges i denne funktion */
     printf("%s Side %d\n", ms1, ++sidenummer);
}

Hvis vi definerer en variabel, for eksempel ms1, på samme niveau som funktioner, så kaldes den et eksternt objekt. (Husk: Et C program består af en række definitioner af eksterne objekter.)

Variablen ms1 vil - uden særlig angivelse af, at den er "public", blive synlig i andre kildetekst filer. De skal blot erklære en extern char * ms1;

Eksempel A-9. Extern declaration

/* External declaration */

extern char * ms1;        /* synlig i hele denne source-fil.*/

bundlinje(){
     extern char * ms2;   /* kun synlig i denne funktion */
     printf("%s\n", ms1);
     printf("%s\n", ms2);
}

Hov! Hvad er nu det - kan man deklarere en "extern" inde i en funktion? Jada, og så er det meningen, at oversætteren skal redde os, hvis vi kommer til at bruge den uden for funktionen. Men deklarationen af extern char *ms2; inde i funktionen forudsætter, at variablen er defineret (og helst også initialiseret, i dette tilfælde!) et andet sted, d.v.s. i en anden kildetekst-fil (source-file).

Bemærk dog, at det stadig accepteres af mange oversættere, at man erklærer en ekstern variabel flere gange med samme navn. Det kan man vist roligt sige er en praksis, som bør undgås. Specielt kan det nævnes, at gcc accepterer at man definerer den samme variabel to eller flere gange, hvis den defineres i ydre niveau og dermed implicit er ekstern variabel!

/* fil 1. Demonstration af uheldig praksis. */
/* programmet oversættes uden advarsler - ikke engang option
 * -Wall, som ellers betyder "giv mig alle advarsler" 
 * (warnings, derfor -W), får gcc til
 * at reagere på denne konstruktion! Men benyttes g++ får man
 * heldigvis en fejl på denne uheldige fremgangsmåde. */

int min_globale_variabel; /* ok, almindelig erklæring af en variabel. */

int min_globale_variabel; /* uha - burde udløse en warning! */
int min_globale_variabel; /* uha - burde udløse en warning! */

int main()
{
     printf("Værdien af min_globale_variabel er %d\n", min_globale_variabel);
     return 0;
}

Det er jo godt nok kedeligt, hvis man tror, at man har to forskellige variable, og oversætteren tror, at der kun er en. Heldigvis findes der et værktøj (ud over c++) som kan afsløre denne og andre sjuskefejl. Lint, en statisk syntakstjekker, har været på banen siden Unix version 7, altså den første offentlige udgave fra 1978. Der findes nu en fri lclint, som er fantastisk god til at gennemgå programmer, og som vel at mærke kan komme igennem include-filer uden brok. Lclint er så pedantisk, at man somme tider ønsker at slå en masse warnings fra, men det er der heldigvis også altid anvisninger på.

fri2c: lclint globviol.c
pluto:/qf3/attic/don/fri ::lclint globviol.c
LCLint 2.4b --- 18 Apr 98

globviol.c:14:5: Variable min_globale_variabel redefined
  A function or variable is redefined. One of the declarations should use
  extern. (-redef will suppress message)
   globviol.c:12:5: Previous definition of min_globale_variabel
globviol.c:15:5: Variable min_globale_variabel redefined
   globviol.c:12:5: Previous definition of min_globale_variabel
globviol.c:12:5: Variable exported but not used outside globviol:
                    min_globale_variabel
  A declaration is exported, but not used outside this module. Declaration can
  use static qualifier. (-exportlocal will suppress message)

Finished LCLint checking --- 4 code errors found

lclint er endog så omhyggelig, at den advarer mig om, at min_globale_variabel faktisk ikke bliver brugt i andre moduler (der er jo heller ikke andre lige for tiden ...) og at jeg derfor burde erklære denne variabel for static. Godt gået.[1]

Hvis man omvendt kun har erklæret alle sine eksterne variable med keywordet extern, så vil gcc beklagende meddele "undefined reference to min_extern". (Se øvelser i kap. 2 for at få mere føling med håndtering af variable i C programmer).

Tilbage til external variables!

Lige som extern kan også static bruges inde i og ude af funktioner.

Resumé: En variabel, som er erklæret i starten af en funktion, er lokal - den er kun synlig i funktionen. Hvis man angiver, at den skal være static, så anbringes den fysisk i en datasektion, som eksisterer i hele programmets køretid. Men den er stadig kun synlig i funktionen, hvori den er defineret.

Så hvad nu hvis man anvender det reserverede ord static udenfor funktionerne, altså lige som eksterne objekter? Jo, så bliver variablens synlighed indskrænket - ikke til en funktion, vi er jo udenfor funktionerne - men til den fil, hvori den er erklæret.

En af de lidt morsomme ting i C sprogets anvendelse af ordene er, at ordet static derved kommer til at betyde, at en variabel er "privat" for det modul, hvori den er erklæret!

Den oprindelige betydning af specifikationen "static" er selvfølgelig, at variablen skal være statisk. Den skal anbringes i sektionen for globale data, statiske data. De kalde statiske, fordi de eksisterer hele tiden, så længe programmet kører. Men for at give programmøren en hjælpende hånd med at kontrollere, hvor den pågældende variabel kan være blevet ændret (hvis der af en eller anden grund skulle opstå fejl i programmet?) så kan synligheden af statiske variable på denne måde begrænses.

Static variabel erklæret inde i en funktion = scope i funktionen, static inde i et modul = scope i modulet.

Et modul kaldes i syntaksbeskrivelser en oversættelsesenhed (eng. translation unit) og består af en eller flere filer (include filer) som oversættes til et objekt-modul.

Variablen i eksemplet ovenfor, sidenummer, kan kun bruges inde i funktionen headerlinje(), men den husker alligevel nummeret fra gang til gang, når funktionen bliver kaldt.

Hvis en funktion som for eksempel strftime(3) gerne vil aflevere en text string med datoen formateret efter callers behov, så er det også nødvendigt at returnere i en buffer, som ligger UDEN for funktionen.

Man kunne somme tider ønske sig, at ordet static blev erstattet af "modul-specifik" eller "privat". Det kan man jo bare gøre selv ved hjælp af #define. I så fald skal man lige huske, at private er et reserveret ord i C++. Man kunne måske i stedet anvende "privat" (uden 'e' til sidst!) eller, bedre, et ord som "hidden" eller ... "local"? SÅ ville der rigtig være rod i semantikken! Problemet er nævnt i forbindelse med revision af C som basis for C++, men som sagt finder private anvendelse i C++ , og det er på en lidt anden måde.

Husk at et C program består af eksterne objekter, som kan være enten funktioner eller variable.

A.2.5. Kontrolvariable for løkker eller loops

Det fremhæves somme tider, at C++ er særlig smart fordi man kan erklære variable der, hvor man har brug for dem. Det er sandelig også en god ting. Man har dog for nylig indført nogle forbedringer (læs rettelser) således at en kontrol-loop variabel, som erklæres i for eksempel en for-løkke, går ud af scope ved afslutningen af den blok, som styres af for løkken. Ellers kunne der nemt opstå noget rod.

Denne fejlkilde er elimineret i C ved at man kan erklære variable i enhver blok (hver gang man skriver en krøllet parentes start, brace start), og denne variabels scope går så indtil blokkens slutning. Nemt og logisk. Denne facilitet anvendes alt for lidt!

Eksempel A-10. En lokal kontrol variabel

/* loopcount.c, demonstration af ad-hoc variabel. */

#include <stdio.h>

char *thisprog;

int main(int argc, char *argv[])
{
    thisprog = argv[0];
    printf("Program %s er startet ... \n", thisprog);
    {
        int jj = 0;              /* jj bliver oprettet */
        while(jj++ < 10)
            printf("For %d. gang: Ih hvor vi kører\n", jj);
    }
                                 /* nu er jj nedlagt "automatisk" */
    printf("Program %s exiter graciøst!\n", thisprog);
    return 0;
}

For at tillade oversætteren at optimere kan det forekomme, at interne blokkes auto-variable faktisk oprettes samtidig med at funktionens stack-frame sættes. Således gør gcc.

A.2.6. Flygtige ukontrollerede variable

En speciel variabel klassifikation er nødvendig for programmer, som deler memory med andre processer eller threads. Her anvendes nøgleordet volatile. En variabel, som er volatile, vil ikke blive optimeret væk, d.v.s. at når programmet skal benytte variabelens værdi, vil der blive foretaget en læsning, og en eventuel gammel værdi i et CPU register vil blive kasseret.

Volatile specifikationen anvendes også på maskiner, som ikke har et separat IO space men benytter en del af RAM som IO-porte.

A.2.7. Oversigt over variabel - anvendelse

Oversigt over anvendelsen af static:

Her er en præcisering af, hvordan man kan definere forskellige slags lokale variable.

Slutbemærkning:

[1]

lint og den systemuafhængige C-oversætter har som oprindelig ophavsmand Stephen C. Johnson, som er en af de kendte oversætter-teoretikere der arbejder på Bell Labs.