En programutvecklingsmiljö under UNIX

Version 0.90 - Let's try it out for real.

Om du hittar några fel eller har andra synpunkter - maila gärna.
/Selander

Här finns en PostScripts-version av detta dokument att skriva ut.


Introduktion

Utbudet av programvara för UNIX är allt för stort för att Datavetenskap skall kunna installera och underhålla allt. Det skulle även bli för dyrt, eftersom många program kräver kostsamma licensavtal. Målsättningen är istället att satsa på ett mindre antal bra och pålitliga produkter, som finns i versioner för alla olika UNIX-dialekter i Datavetenskaps maskinpark och som man kan hålla med dokumentation för och handledning i.

Detta dokument tar upp Datavetenskaps rekommenderade baspaket för programutveckling under UNIX. Tanken är att denna arbetsmiljö introduceras på studenternas första UNIX-kurs, för att sedan "hänga med" på följande kurser - oavsett i vilket datorsal eller språk laborationerna löses. Paketet är avsett för programutveckling i C eller C++, men alla program förutom kompilatorn kan även användas till andra språk.

I arbetsmiljön ingår editorn XEmacs, C-kompilatorn GCC, kommandorads-debuggern GDB, den fönsterbaserade debuggern DDD och det specialiserade kodutskrivningsprogrammet GNU Enscript. Dessutom ingår makefile-tillverkaren Mkmf och det praktiska administrativa programmet GNU Make. Tillsammans utgör dessa program en god miljö för att programmera i C, både små kodsnuttar och större projekt.

Innehåll
Introduktion
Innehåll
Hur ska denna handledning användas?
Vad är GNU? Gnu's Not Unix!
XEmacs
GNU C Compiler (GCC)
GNU Debugger (GDB) och The Data Display Debugger (DDD)
GNU Make och Mkmf
GNU Enscript
Ordlista

Hur ska denna handledning användas?

Du skulle kunna sträckläsa denna handledning från pärm till pärm (om den nu hade haft riktiga pärmsidor). Alltid skulle något fastna. Fast i realiteten skulle du nog bara bli mäkta förvirrad av alla kryptiska finesser, optioner och genvägar. Däremot kan det vara nyttigt att i lugn och ro läsa igenom inledningen till respektive verktygs avsnitt. På så sätt får du grepp om vad du kan (och ska) använda dem till. De påföljande, mer invecklade, passagerna är lämpligast att spara tills du sitter vid datorn och hela tiden kan se vad texten refererar till och själv prova det som tas upp.

Denna handledning ger ingen omedelbar hjälp eller total vägbeskrivning. Den är tänkt som ett stöd och komplement till eget engagemang och nyfikenhet, utan vilka du inte kommer någonstans i dina studier, oavsett inriktning.

Vad är GNU? Gnu's Not Unix!

De flesta programmen i denna arbetsmiljö är en GNU-programvara. Vad är då GNU? GNU är ett projekt med målet att kunna erbjuda ett komplett UNIX-kompatibelt mjukvarusystem gratis.

Projektet startades i mitten av åttiotalet av Richard Stallman i ett försök att återskapa den sammarbetsvänliga hacker-anda som existerat programmerare emellan under sextio- och sjuttiotalet. Stallman kunde inte dra jämt med att programmerare vid den här tiden började ta betalt för sina program och att det infördes lösenord på datorsystemen. Vid ett tillfälle försökte han till och med få datavetenskap vid MIT att ta bort sitt nyinstallerade lösenordssystem genom att vägra dem den senaste versionen av hans texteditor Emacs. Stallman följde "the Hacker etics", att datortillgång och information ska vara fri, för allas bästa.

Idag har GNU-projektet resulterat i att mängder av GNU-programvara skrivits, en del av anställda inom GNU-projektet och organisationen Free Software Foundation (FSF), resten av frivilliga programmerare från hela världen. Projektet bygger på donationer av pengar och hårdvara och frivilliga arbetsinsatser av programmerare.

Det är ingen slump att GNU är UNIX-kompatibelt. UNIX var det logiska valet för professionella programmerare under åttiotalet och man ville att det skulle vara lätt för en användare att gå från UNIX till GNU. Det UNIX saknade menade GNU-folket att de kunde lägga till i sin version. För att delta i GNU-projektet behöver du bara skriva en gratis variant av någon systemprogramvara på ditt lokala UNIX-system. Om du kan få din variant att fungera utan problem på ditt system bör det även fungera på ett GNU-system. UNIX-kompatibiliteten har alltså inneburit att man inte behövt samordna arbetsinsatser från programmerare i många länder, utan bara samla ihop frukten av deras arbete.

Utgångspunkten för GNU var att kunna erbjuda datorallmänheten ett gratis operativsystem, komplett med allt från en kärna för de flesta datorarkitekturer, till alla upptänkliga användar-applikationer. Idag finns GNU-versioner av de flesta program man kan behöva och de som saknas är ofta planerade, men kärnan, the GNU-Hurd, är ännu inte helt färdig. I början av nittiotalet skrev dock finländaren Linus Torvalds Linux, en UNIX-inspirerad kärna för PC. Sedan dess har Linux portats till många andra arkitekturer och det finns nu en stor samling olika Linux-paket att installera på din dator. Mycket av programvaran i dessa paket är GNU-programvara och även om GNUs egen kärna inte är färdig går det idag att ha ett datorsystem med helt gratis mjukvara genom att kombinera Linux-kärnan med GNU-programvaran.

GNU-program är dock inte Public Domain, de är istället vad de kallar "Copyleft". Båda innebär att programmen är gratis för vem som helst att använda eller förändra, men "Copyleft" innebär att programmet, även efter förändringar, måste vara fritt för andra att kopiera. GNU-folket menar att som Public Domain skulle någon kunna göra en del förbättringar av ett program och sedan ta betalt för den nya versionen, något som inte är förenligt med GNU-policyn.

Mer information finns på http://www.gnu.org/.

XEmacs

Under sextiotalet använde den första generationen hackers vid MIT AI-lab TECO ([paper] Tape Editor and COrrector) för att editera filer. TECO var skriven av en hacker för andra hackers och var känd för sina kraftfulla, programmeringslika finesser och sin mycket invecklade syntax, t ex sorterar J^P$L$$ alla rader i en fil. För att förenkla användningen av TECO skrev Richard Stallman i början av sjuttiotalet ett paket TECO-macros som han kallade Emacs (Editing MACroS). Inte långt därefter skrev han en fristående Emacs i LISP. Sedan dess har det gjorts otaliga Emacs-versioner till olika system. Stallmans ursprungliga Emacs ligger till grund för dagens GNU Emacs som jämte GCC är de mest spridda och använda GNU-programmen.

Emacs är en avancerad, själv-dokumenterande, utbyggbar editor, lätt att skräddarsy efter eget tycke och smak. Förmodligen kommer du aldrig att skriva några nya tillägg i LISP till Emacs och du kommer säkert inte göra några större modifieringar av standardkonfigurationen, men den omfattande, inbyggda hjälpen och många av Emacs rena editeringsfinesser kommer du nog att bli väl förtrogen med. Förutom att editera filer kan du även använda Emacs som mail-, news- och webläsare, eller varför inte både kompilera och debugga dina program inifrån Emacs? Många programmerare spenderar det mesta av sin inloggade tid i Emacs och har alltid en Emacs igång.

Haken med Emacs är att det är stort och resursslukande. Elaka tungor har uttytt Emacs som "Eventually Malloc()'s All Computer Storage", "Emacs Makes A Computer Slow" och "Escape Meta Alt Control Shift", det sistnämnda på grund av att de flesta av Emacs kommandon involverar någon av de nämnda tangenterna i kombination med andra. Emacs är dock lätt att komma igång med, vilket man inte kan säga om dess främsta konkurrent vi, som är liten och snabb men har en hög inlärningströskel (för den vi-vane finns ett specialläge i XEmacs kallat Viper som simulerar vi. Du startar Viper genom att välja Emulate vi under Apps-menyn).

Den version av Emacs som ingår i denna arbetsmiljö är XEmacs, en vidareutveckling av GNU Emacs. Upphovsmännen till XEmacs tyckte helt enkelt att de kunde göra en bättre Emacs än GNU/FSF. XEmacs är dock till största delen helt kompatibelt med GNU Emacs och alla grundläggande funktioner är helt lika.

När du slår ett xemacs så kommer snart ett XEmacs-fönster upp på skärmen. Detta fönster kan du flytta runt och ändra storlek på precis som andra XWindows-fönster. XEmacs-fönstret är i sin tur uppdelat i olika delar. Längst upp har du en rad med menyer, från File till vänster till Help längst ut till höger. Under menyerna kommer en rad med ikoner, från Open till News, därefter kommer ett, eller flera, delfönster med text. Dessa kallas buffrar och det är i dem själva arbetet sker. Längst ned i varje buffer finns en statusrad, där det bland annat står vilken fil som visas i bufferten och var i filen du befinner dig. Under den nedersta statusraden finns en minibuffer där meddelanden från XEmacs visas, och där du matar in information till olika kommandon.

Ibland är det inte så fördelaktigt med XEmacs-fönstret, t ex om du sitter inloggad via modem. Då kan du starta XEmacs med växeln -nw (no window) så får du upp XEmacs i textläge, utan något eget fönster. Du mister då menyerna och ikonerna, men i övrigt fungerar allting likadant.

Innan vi går vidare är det bäst att ta upp konventionen för att skriva olika kombinationer av specialtangenter och vanliga tangenter i Emacs-dokumentation. Om du tittar i menyerna ser du att många av valen har motsvarande snabbkommando skrivna till höger om sitt namn, t ex Exit XEmacs längst ned under File som har snabbkommandot C-x C-c. Detta ska utläsas som control-x control-c, dvs du håller inne control-tangenten och trycker på x och sedan håller du inne control och trycker på c. Utanför Emacs-världen är det även vanligt att skriva kombinationer av control och andra tecken, t ex c, som ctrl-c eller bara ^c.

I XEmacs används även meta-tangenten ofta. På ett Sun-tangentbord är detta tangenten märkt med ett ruter-tecken. På SGI- och IBM-tangentbord är vänster Alt-knapp meta-tangent. Meta-kombinationer skrivs M-tangent, t ex M-f som hoppar fram till slutet av nästa ord. Även escape-tangenten brukar fungera som meta.

Vi har redan nämnt att du avslutar XEmacs med C-x C-c. Om det finns någon modifierad buffer blir du då tillfrågad i minibuffern om den ska sparas. För att explicit spara innehållet i en buffer använder du C-x C-s och för att öppna en fil trycker du C-x C-f varvid du tillfrågas om filnamnet i minibuffern. Finns inte filen så öppnas en ny, tom buffer för en fil med det nya namnet. Du kan även ange den fil du vill öppna som argument till XEmacs när du startar det.

Om du gör ett visst antal tangenttryckningar under arbetet med en fil utan att spara den autosparar XEmacs filen under samma filnamn, fast med ett #-tecken före och efter. Skulle XEmacs av någon anledning krascha är det bra att kunna fortsätta med en autosparad fil. När du sparar ditt arbete med C-x C-s tas eventuell autosparad version bort. Första gången du sparar en fil under en editeringssession görs det dessutom en säkerhetskopia av filen innan den skrivs över med de nya förändringarna. XEmacs kopierar filen till samma namn men med ett tilde (ett ~-tecken) efteråt. Bli sålunda inte förvånad om du editerar filen lab1.c och hittar filerna lab1.c~ och #lab1.c# i samma katalog.

Om du öppnar en fil (eller startar XEmacs med en fil som argument) och det står "Auto save file is newer; consider M-x recover-file" i minibuffern så har XEmacs upptäckt att det finns en autosparad version av filen som är nyare än den du vill öppna. Om du vill öppna den autosparade versionen trycker du M-x recover-file och sedan namnet på filen utan några #-tecken, dvs du skriver samma filnamn om igen. På motsvarande sätt kan du ångra alla ändringar du gjort i en fil sedan du öppnade den genom att välja Revert buffer ... under File-menyn, eller trycka M-x revert-buffer. Då öppnas den senast sparade versionen av din fil och alla senare ändringar går förlorade.

Nästan genomgående i XEmacs gäller att du kan använda tab-tangenten eller mellanslag för att fullborda ett indata, t ex ett filnamn eller ett kommando. Om du exempelvis vill göra ovan nämnda M-x revert-buffer skriver du M-x rev och trycker sedan tab (om du trycker tab direkt efter r eller re visar XEmacs en lista över alla kommandon som börjar med r, respektive re). XEmacs fyller nu i er själv, sedan finns det flera möjligheter. Du måste ge XEmacs en ledtråd och skriver därför dit t:et i revert och trycker på tab. revert är en unik början och XEmacs fyller själv på med -buffer, varpå du bara behöver trycka enter. På motsvarande sätt behöver du oftast bara ange början på ett filnamn eller början av ett buffernamn, trycka tab eller mellanslag och sedan enter, när XEmacs efterfrågar något. Det går även att trycka tab eller space igen.

Skulle du råka göra något galet fungerar C-x u som ångra. Detta går att göra i flera (många) steg - ofta kan du ångra alla ändringar du gjort sedan du öppnade filen. Ångrar du för många steg räcker det med att flytta på markören ett steg och sedan börja ångra igen. De tidigare ångringarna är nu själva modifieringar som går att ångra!

Om minibufferten plötsligt frågar efter något kryptiskt som du verkligen inte har bett om, men förmodligen råkat utföra ändå, är C-g bra att komma ihåg. C-g avbryter kommandon i XEmacs men är annars helt ofarlig att trycka på, det enda som händer är att det piper. Om XEmacs verkar helt låst kan du prova att upprepa ett par C-g. Du kan även prova escape tre gånger. Hjälper inget av detta hjälper inget alls.

C-h är nyckeln till den inbygda hjälpen, t ex C-h k som sedan tar valfri tangentkombination i minibuffern och visar en beskrivning av vad den utför. Andra bra hjälp-kommandon är C-h v och C-h f som ger en beskrivning av påföljande variabel, respektive funktion du knappar in. All hjälp kan även nås via Help-menyn längst uppe till höger. Botanisera gärna på egen hand bland dess undermenyer. Den nyttigaste hjälpen för nybörjare hittar du i undermenyn Basics - Tutorial under Help. Den kan även nås via C-h t. Denna Tutorial innehåller en introduktion till alla grundläggande kommandon för navigering och editering i XEmacs, blandad med direkta övningar på allt som tas upp. Jag rekommenderar verkligen att du tar dig tid att gå igenom Tutorial-filen, särskilt som den tar upp de grundläggande förföyttningskommandona vilka jag bara som flyktigast tänker lista nedan:

C-v Hoppa framåt en skärmsida
M-v Hoppa bakåt en skärmsida
C-l Skriva om skärmen med aktuell markörposition i centrum
C-b, vänsterpil Gå ett tecken till vänster
C-f, högerpil Gå ett tecken till höger
M-b Hoppa ett ord till vänster
M-f Hoppa ett ord till höger
C-p, pilupp Hoppa en rad uppåt
C-n, pilner Hoppa en rad neråt
C-a Hoppa till början av raden
C-e Hoppa till slutet av raden
M-a Hoppa till början av en mening
M-e Hoppa till slutet av en mening
M-< Hoppa till början av filen
M-> Hoppa till slutet av filen
delete, backspace Ta bort tecknet till vänster om markören
C-d Ta bort tecknet till höger om markören
M-backspace Ta bort ordet till vänster om markören
M-d Ta bort ordet till höger om markören
C-k Ta bort allt från markören till slutet av raden
M-k Ta bort allt från markören till slutet av meningen
C-y Rycka tillbaka (yank) senaste borttagna text
M-y Endast efter C-y, bläddrar igenom tidigare borttagna texter
C-x u, C-_ Ångra
C-x C-f Öppna en fil
C-x C-s Spara en fil
C-x C-b Lista buffrar
C-x C-c Avsluta XEmacs

Sök och ersätt är vanliga textersättningskommandon. I XEmacs söker du framåt med C-s och bakåt med C-r. Söker du framåt och når slutet av dokumnetet hoppar sökningen dock till början och fortsätter, så du kan söka igenom hela dokumentet med endast ena riktningens sökfunktion. När du tryckt C-s eller C-r får du skriva in sökordet i minibuffern. Det fina med XEmacs sökfunktion är att redan när du skrivit första bokstaven i ordet så söker XEmacs rätt på första förekomsten av den bokstaven och markerar den. För varje ytterligare bokstav du lägger till letar XEmacs efter första förekomst av just den kombinationen. Om den inte förekommer alls står det Failing I-search och du kan sudda bokstäver och prova något annat. Om du hittat en förekomst av ett ord och vill leta efter nästa trycker du C-s respektive C-r igen så letar XEmacs efter nästa förekomst. Om du hittat rätt och vill gå ur sökläget trycker du på enter och kan fortsätta arbeta från den nya markörpositionen. Om du vill hoppa ur sökläget och gå tillbaka dit du startade trycker du C-g. För att söka och ersätta trycker du M-%. Du tillfrågas då om sökord och ersättningsord och får sedan för varje förekomst av sökordet ange med y och n om du vill byta ut förekomsten eller inte. Vill du byta ut alla förekomster utan att tillfrågas trycker du !. Även sök och ersätt avbryts med C-g.

Om du är osäker på vad något kommando heter, vilket är lätt hänt med de mer ovanliga XEmacs-kommandona med långa namn, eller om du vill leta efter något speciellt kan du använda Apropos. Denna "apropå"-funktion, som finns i två varianter; C-h a för bara kommandon och den totala M-x apropos, tar ett nyckelord och visar alla funktioner som matchar detta ord. Om du t ex är ute efter någon av filhanteringsfunktionerna kan du trycka M-x apropos, följt av enter. Du frågas nu efter ett sökord i minibuffern. Skriver du file och trycker enter får du upp en lista över allt med filanknytning i XEmacs. I listan kan du sedan klicka med mitten-musknappen på de saker du är nyfiken på för att få mera information.

Vissa kommandon öppnar en ny buffer. För att komma tillbaka till den buffer där du var innan, eller för att komma till en annan buffer, kan du antingen välja önskad bufer direkt under Buffers-menyn eller trycka C-x C-b, som visar en lista över aktiva buffrar, och därefter välja vilken buffer du vill gå till genom att hålla muspekaren över dess namn och trycka på mittknappen. Ett annat, snabbare sätt är att trycka C-x b och skriva namnet på önskad buffer i minibuffern. Oftats behöver du bara skriva ett par tecken och sedan trycka på tab. Om du har flera buffrar igång men vill att bara en ska visas i XEmacs-fönstret placerar du markören i önskad buffer med musen och trycker C-x 1. För att ta bort en buffer trycker du C-x k och skriver dess namn.

Fördelen med flera buffrar är att du kan editera två filer samtidigt, t ex klippa över text från fil A till fil B. Du kan dessutom inifrån XEmacs kompilera det program du håller på att utveckla genom att trycka M-x compile, eller genom att välja Compile... under Tools-menyn. I minibuffern presenteras då default-kompileringskommandot make -k. Om du gjort dig en Make-fil är det bara att trycka enter, annars får du ändra make -k till aktuellt kompileringskommando, t ex gcc -g -Wall -o program kod.c. Det öppnas nu ytterligare en buffer där du får utdatat från kompileringen. I bästa fall står det endast att kompileringen lyckades, annars står det vilka varningar och fel som uppstod. Då är det bara att skriva C-x b, välja källkodsbuffern och börja felsöka. Debug (GDB) under Tools-menyn låter dig på motsvarande sätt debugga ditt program med GDB innifrån XEmacs.

Det finns flera olika editeringslägen i XEmacs. Dessa går att välja explicit, med M-x lägesnamn. Oftast är det dock onödigt, eftersom XEmacs gör en kvalificerad gissning när filen öppnas. Om du t ex öppnar en fil med C-källkod hamnar du i det speciella C-läget. I detta läge blir tab-tangenten en automatisk indenteringstangent som hoppar in ett visst antal indenteringssteg beroende på den kod du just skrivit. XEmacs analyserar varje kodrad du skriver för att avgöra hur mycket påföljande rad ska indenteras. Istället för att trycka enter för att bryta en rad och sedan tab för att komma rätt på nästa, kan du trycka C-j, vilket är ett kort-kommando för enter och tab. För din egen och dina handledares skull, använd XEmacs indenteringsfunktion. Din kod blir mycket mer läsbar på det sättet.

Under Syntax Highlightning i Options-menyn kan du ställa in om du vill ha syntaxmarkering med hjälp av typsnitt eller färger (Fonts eller Colors) och hur känslig den ska vara (Least till Most). Normalt är syntaxmarkeringen avslagen, men den kan erbjuda stor hjälp när du sitter och skriver kod, eftersom den tilldelar olika syntaktiska element olika färger, t ex kan reserverade ord i C få en färg medan dina egendefinierade variabler och funktionsnamn får andra färger.

Förutom ett C-läge och flera lägen för textredigering finns det bland annat editeringslägen för C++, LISP, HTML, LaTeX, Assembler, Java, Pascal, Perl och VRML, för att bara nämna de lägen som ni under utbildningen kan komma att ha nytta av.

Mer information hittar du med Control-h, under Help-menyn eller på http://www.xemacs.org/.

GNU C Compiler (GCC)

GCC är ett av de tidigaste GNU-programmen. Det behövdes nämligen en gratis C-kompilator för att kunna kompilera källkoden till alla andra GNU-program. Arbetet med GCC är dock långt ifrån avslutat. Det kommer fortfarande nya uppgraderingar och det utvecklas kontinuerligt "Front ends" för andra språk. En "Front end" är den delen som parsar din källkod och översätter den till någon form av mellankod. Olika "Front ends" kan producera samma mellankod för olika programmeringsspråk. Denna mellankod översätts sedan av en "Rear end" till exekverbar maskinkod för aktuell datorarkitektur. Vad GNU-projektet siktar mot är att för alla olika datorplattformar ha en fungerande GCC-"Rear end" och ett gäng "Front ends" för populära språk.

Dagens GCC utgör en fullfjädrad kompilator för C, C++ och Objective-C. Normalt kallas dock GCC g++ när den används som C++-kompilator. GCC stödjer både Kernighan & Ritchie C och ANSI-C.

I grunden exekverar du GCC med en, eller flera, filer som argument. Beroende på vilken ändelse filen/filerna har gör GCC olika saker:

.c C-källkod; preprocessa, kompilera, assemblera, länka
.C, .cc, .cxx C++-källkod; preprocessa, kompilera, assemblera, länka
.m Objective-C-källkod; preprocessa, kompilera, assemblera, länka
.i preprocessad C; kompilera, assemblera, länka
.ii preprocessad C++; kompilera, assemblera, länka
.s Assembler-källkod; assemblera, länka
.S Assembler-källkod; preprocessa, assemblera, länka
.h Preprocessor-fil; dyker oftast inte upp på kommandoraden
.o Objekt.fil; länka
.a Arkivfil; länka

Preprocessorn tar hand om alla rader i din källkod som börjar med #, t ex #include ...-rader som preprocessorn ersätter med motsvarande .h-fil och #define ALIAS VALUE-rader som byter ut alla förekomster av ALIAS mot VALUE. Om du till exempel använder Pi ofta kan du i början av ditt program inkludera raden:

#define PI 3.14159

Sedan kan du använda macrot PI i alla dina beräkningar. På så sätt behöver du bara ändra define-raden om värdet på Pi skulle ändras, istället för att behöva ändra överallt i källkoden och riskera att missa något ställe och få felaktiga beräkningar (i math.h finns redan M_PI definerat som Pi med tjugo decimaler). #-rader ingår egentligen inte i C utan preprocessorn är bara en arbetsbesparande finess som lagts till för att förenkla en programmerares arbete. Med optionen -E genomför GCC endast preprocessorfasen och skippar resten, varje input-fil, t ex en .c-fil, ersätts av en motsvarande .i-fil.

Kompileringen tar den preprocessade källkoden och översätter den till assembler-kod. Om du vill att GCC ska stanna efter kompileringsfasen kan du ange optionen -S. gcc -S kaka.c resulterar i att assemblerkoden för programmet kaka.c läggs i en fil kaka.s.

Assembleringen tar assemblerkod och assemblerar den till reallokerbar maskinkod. Med -c abryter GCC exekveringen efter assemblering och länkar inte. Istället får du en .o-fil (objekt-fil) för varje fil på kommandoraden.

Länkaren sätter till sist ihop alla aktuella objekt-filer, arkiv-filer och nödvändiga biblioteksrutiner till en exekverbar binärfil. Om inget annat anges får den namnet a.out.

Exekveringsordningen för GCC skulle man sålunda kunna måla upp så här: .c -> .i -> .s -> .o -> körbart program. Du kan som argument till GCC blanda filer som kommit olika långt i kompileringscykeln. Det är detta man utnyttjar när man anropar biblioteksrutiner från sina program. Biblioteken är egentligen en massa nyttiga och ofta använda kodsnuttar som kompilerats och assemblerats till en samling .o-filer samlade i .a-arkiv. Genom att anropa dessa, t ex printf som finns i stdio-biblioteket, slipper du uppfinna hjulet om och om igen. Du vet att du blivit en riktig C-hacker när du har samlat ihop till ett eget litet bibliotek av rutiner du använder för jämnan i dina program.

Normalt använder du aldrig gcc direkt vid prompten. Istället använder du dig av Make (se avsnittet om GNU Make). Du måste dock ändå ange olika optioner till GCC, vare sig du kör det för hand eller använder en makefile. Till GCC finns hundratals optioner. Dessa är dock de viktigaste och mest använda:


-o filnamn


ange att GCC ska stoppa resultatet av sin körning i en fil men namn filnamn istället för i respektive default-namn

-E

stoppa körningen efter preprocessing (se ovan)

-S

stoppa körningen innan assemblering (se ovan)

-c

stoppa körningen innan länkning (se ovan)

-v

verbose, GCC meddelar hela tiden vad det egentligen gör

-ansi

Göra GCC helt kompatibel med ANSI-C i de fåtal fall den inte är det

-pedantic

GCC varnar för allt som inte följer strikt ANSI

-Dmacro

Skicka med en macro-defintion, oftast i debug-syfte

-llibrary

Använda biblioteket library under länkningen

-Ldir


Lägga till katalogen dir till listan av kataloger där GCC letar efter biblioteksfiler (används ofta tillsammans med -l)

-Idir


Lägga till katalogen dir till listan av kataloger där GCC letar efter #include-filer

-Wall



Warning-all, aktiverar en stor mängd varningsflaggor som genererar många nyttiga varningar för möjliga oegentligheter i din kod. Gör det till en vana att alltid editera bort alla varningar ur din kod.

-g

Producera debug-information för debugging med t ex GDB

-pg

Generera extra kod för profilinformation för analys av ditt program med gprof

-O

Lägsta gradens optimering

-O2

Mer optimering

-O3


Ännu mer optimering. Om programmet uppför sig annorlunda efter optimering har du förmodligen ett mindre minnesfel någonstans i ditt program

-x språk

Ange explicit vilket format input-filerna är i, bra om filnamnen inte följer ändelse-konventionerna

Mer information hittar du med man gcc och ginfo gcc.

GNU Debugger (GDB) och The Data Display Debugger (DDD)

I början av femtiotalet felsökte en grupp Harvard-programmerare i USA ett icke-fungerande program i veckor utan att hitta något fel. Till sist avslöjade en kontroll av aktuell dators innanmäte att en död insekt olyckligtvis blockerade ett relä och därför orsakade felet. Så fort insekten avlägsnats fungerade programmet felfritt. Enligt legenden gav detta upphov till termen debugging (avlusning). Egentligen är metaforen mindre lyckad, eftersom debugging antyder att det inte är programmerarens fel att programmet inte fungerar. -"Elakartade småkryp (bugs) har smugit sig in koden när jag tittade åt ett annat håll." Naturligtvis är dock våra buggar vårt eget ansvar och mycket tid och energi har lagts ned på att förenkla avlusningsfasen av ett programmeringsprojekt för den enskilde programmeraren.

En debugger är ett verktyg för felsökning av (felaktiga) program. Det används för att övervaka och kontrollera körningen av ett program. Det utmärkande för en debugger är att den låter oss se vad som händer i ett program under körning eller när det kraschat. Du kan se vilka värden olika variabler antar och var olika pekare pekar. Du styr själv när programkörningen ska stoppas för kontroll och när den ska återupptas igen. Du kan stega dig igenom den kritiska biten av ditt program och se precis vad som går fel.

Lär du dig att använda en debugger på inledande UNIX-kurser har du igen det på svårare labbkurser i senare delar av utbildningen. Alternativet till att utnyttja en riktig debugger är att överösa sitt program med tonvis av testutskrifter (printf är den enklaste men också den mest begränsade debuggern). Att gå igenom och tolka dessa utskrifter är dock i regel mer tidsödande än att använda en debugger man är väl förtrogen med. Dessutom brukar tilläggandet och borttagandet av testutskrifter i programmet medföra fler nya buggar än de gamla man hittar...

GNU Debugger, GDB, är en vida spridd kommandoradsorienterad debugger, dvs den har ett kommandoradsgränssnitt. DDD, the Data Display Debugger, är ett grafiskt skal till GDB (DDD stödjer även två andra kommandoradsdebuggers, DBX och XDB, men de tas inte upp här). DDD ger egentligen ingen utökad funktionalitet, det är ju ändå GDB som körs, men DDD ger ett betydligt mer lättanvänt användargränssnitt. GDB är skrivet och underhållet av GNU-projektet medan DDD är en vidareutveckling av två tyska studenters examensarbeten.

För att kunna debugga ett program effektivt måste det innehålla debug-information. Ska du debugga ett program utan debug-information måste du hela tiden ange de minnesadresser du är intresserad av. Detta är långt ifrån enkelt. Finns debug-information kan du istället referera till dina egna variabel- och funktionsnamn och följa exekveringen i din egen källkod. Debug-information lägger du till i ditt program med flaggan -g vid kompileringen. För bästa resultat med GDB bör du kompilera ditt program med gcc -g (GNU-program favoriserar andra GNU-program). På detta sätt lägger kompilatorn in symboltabellen i objektfilen, med information om dina variabler och funktioner. Även information om korresponderande källkodsrader och resulterande maskinkodsinstruktioner hamnar där. Detta gör naturligtvis ditt program en smula större, men när du väl hittat och avlägsnat dina buggar kan du kompilera om programmet utan -g för att vinna några bytes. På samma sätt är det säkrare att vänta med att lägga till optimeringsflaggor vid kompilering tills du debuggat färdigt. Visserligen klarar GCC att kombinera -g och -O, vilket många andra kompilatorer inte gör, men under optimeringen omarrangerar kompilatorn din kod en smula, vilket gör att exekveringen i GDB/DDD inte verkar följa din källkod (vilket den heller inte gör - den följer en smartare, optimerad kod). Alltså; kompilera med -g men utan optimering tills du är säker på att programmet fungerar felfritt. Sedan kompilerar du om det utan -g men med alla adekvata optimeringsflaggor du finner nödvändiga.

Observera att GDB/DDD endast kan debugga program skrivna i C++ om de är kompilerade med GCC/G++.

Att i denna begränsade handledning ta upp allt du kan göra med GDB och DDD är praktiskt omöjligt. Istället kommer jag att gå igenom de vanligaste funktionerna och överlåta till din nyfikenhet och experimentlusta att sköta resten. Det förmodligen bästa sättet att snabbt lära sig DDD är att först starta det och planlöst leka med det. Sedan skriver du ett sexraders "Hello world"-program (ett program som bara skriver ut "Hello world" på skärmen. Du kan även testa exemplet i slutet av detta avsnitt.), startar DDD med det som argument och läser igenom resten av detta avsnitt samtidigt som du testar de olika funktionerna på ditt program. Kanske måste du utöka det, t ex med några underfunktioner, för att kunna tillgodogöra dig de mer avancerade finesserna i DDD. Prova dig fram!

Både GDB och DDD kan startas utan några argument, varvid programmet som ska debuggas måste öppnas inifrån debuggern, men normalt anges det felaktiga programmet som argument:

ddd bad_program eller gdb bad_program

Om ditt program core-dumpar kan du starta GDB eller DDD med både program och core som argument:

ddd bad_program core eller gdb bad_program core

Då hamnar du direkt i den situation där programmet kraschade. Du behöver alltså inte köra och framkalla kraschen inifrån debuggern utan kan börja nysta upp situationen direkt på olycksplatsen så att säga. Du ser direkt var i din kod som kraschen orsakades, eller så står det i vilken subfunktion programmet löpte amok. Du kan då stega uppåt i anropshierakin med Up-knappen i DDD, eller med kommandot up i GDB, tills du hamnar i din egen kod istället för underliggande systemfunktioner.

Ibland kan det vara bra att kunna felsöka ett körande program (ett program behöver ju inte krascha för att vara felaktigt). Då kan du starta GDB eller DDD med
aktuellt program och dess processnummer:

ddd bad_program 6081 eller gdb bad_program 6081

När DDD startas öppnas ett fönster med tre olika delar; datavyn, källkodsvyn och GDB-konsolen.

GDB-konsolen är en GDB-prompt, precis som den skulle se ut i ett vanligt xterm-fönster om du startat GDB istället för DDD. På denna konsol kan du köra GDB utan att utnyttja någon av DDD's ytterligare finesser. Den vana användaren uppehåller sig mest i detta del-fönster. Nybörjaren tycker däremot att GDB är för svår och knappar sig bara runt med musen i övriga delar av DDD-fönstret.

Källkodsvyn visar en bit av källkoden till ditt program. Under denna del sitter en dialogruta och sju knappar. Du kan specificera olika syntaktiska delar av din kod genom att klicka på dem i kodfönstret eller genom att skriva dem i dialogrutan.

Med Lookup ()-knappen kan du leta efter var funktioner är definierade. Det går dock bara med de funktioner som finns definierade i din kod, du kan inte slå upp externa systemfunktioner.

Med Break at ()-knappen sätter du ut en breakpoint på aktuell rad i din kod. När du kör programmet och det kommer till en breakpoint stannar det upp. Med Clear at ()-knappen tar du bort breakpoints på motsvarande sätt. Detta är dock långtifrån allt du kan göra med breakpoints. Om du trycker in höger musknapp över en breakpoint kan du deaktivera den utan att ta bort den, aktivera en deaktiverad breakpoint och till och med ange att en breakpoint ska ignoreras ett visst antal gånger innan den stoppar exekvering eller att den ska avbryta programflödet endast om ett visst villkor är uppfyllt. Håller du in höger musknapp över radnumren till källkoden kan du sätta ut temporära breakpoints som tas bort automatiskt så fort de stoppat exekvering. Vill du veta mer om hur man kan använda breakpoints effektivt skriver du help breakpoints på GDB-konsolen.

Print ()-knappen skriver ut värdet för aktuellt syntaktiskt element på GDB-konsolen och Display ()-knappen symboliserar ett syntaktisk element grafiskt i datavyn i DDD. Ett annat sätt att se värdet för en variabel eller datastruktur är att helt enkelt hålla muspekaren över den i källkoden någon sekund, en liten ruta med aktuellt värde poppar då upp.

Find<< ()- respektive Find>> ()-knappen söker efter angivet element bakåt respektive framåt i koden.

Det finns mängder av genvägar och snabbkommandon i DDD. Testa t ex vilka menyer du får upp om du trycker på höger, respektive vänster, musknapp över olika delar av DDDs vyer. En tips för den nyfikne nybörjaren är att hålla inne F1-tangenten och föra muspekaren över de olika knapparna i DDD, du bör dock flytta musen ganska långsamt för bästa resultat.

Till källkodsvyn hör också en liten verktygslåda som normalt flyter uppe till höger i samma vy. Denna verktygslåda innehåller 14 knappar:
Run-knappen startar exekveringen av programmet som skall debuggas. Vill du ange argument till programmet ska du istället välja Run under Program-menyn. Då kommer en dialogruta, där du kan skriva in argumenten, upp. Du kan även skriva run följt av eventuella argument direkt på GDB-konsolen.

Interrupt-knappen avbryter exekvering, du kan även använda ctrl-c.

Step-knappen stegar en rad framåt i koden. Hittar den ett funktionsanrop stegar den ned i underfunktion. Next stegar däremot över anrop och hoppar alltså alltid till nästa kodrad. Stepi och Nexti gör precis samma sak som Step och Next, fast instruktionsvis istället för radvis. De är främst tänkta att användas i maskinkodsvyn, som du öppnar genom att välja Display Machine Code i Options-menyn.

Cont fortsätter exekveringen efter en breakpoint eller ett interrupt.

Finish fortsätter exekveringen tills aktuell funktion tar slut. Om funktionen returnerar något skrivs detta värde ut på GDB-konsolen.

Up och Down stegar upp och ned i anropsstacken om möjligt. Dvs Up stegar upp till den kodrad där aktuell funktion anropades och Down stegar ned igen. Ett annat sätt att undersöka anropsstacken är att välja Backtrace under Status-menyn, ett tredje är att köra kommandot bt eller where på GDB-konsolen. Du ser då hur programmet kommit till stället där det är.

Back och Fwd hoppar till föregående, respektive nästa, källkodsposition.

Edit låter dig editera källkoden under pågående debugging-session.

Kill avslutar pågående exekvering för gott.

Datavyn är kanske den främsta fördelen med DDD. I denna vy kan du visa datastrukturer grafiskt, vilket gör dem betydligt mycket lättare att tolka och felsöka än om man måste slå upp varje del för sig från GDB-prompten och få dem textuellt representerade. Varje data visas som en liten display där namn och värde redovisas. Display ()-knappen vid källkodsvyn låter dig lägga till displayer till datavyn. Du kan även klicka på New Display i knappraden vid datavyn. Du får då upp en dialogruta där du kan ange den variabel eller uttryck du vill få visad. Under Data-menyn finns de två mycket praktiska valen Display Local Variables och Display Arguments som lägger till aktuell funktions alla lokala variabler respektive argument som displayer i datavyn. Prova även gärna att välja More Status Displays från Data-menyn. Testa sedan de olika alternativen. Vad visar de? Vilka kan du ha nytta av? Tänk på att det tar tid att uppdatera displayerna i datavyn. Tar du bort dem så fort du inte behöver dem längre så går saker och ting lite mindre segt. En annan mycket praktisk finess är alias-detekteringen. Den suger tyvärr så mycket kraft att den normalt är avslagen. Debuggar du ett program med en komplex pekarstruktur som du vill ska visas i datavyn kan det dock vara läge att slå på den genom att välja Detect Aliases under Data-menyn. DDD går då igenom minnespositionerna för varje objekt i datavyn och kontrollerar om två, eller flera, displayer delar adress. Gör de det slår DDD ihop dem till en display istället för två, vilket ju är mer korrekt eftersom två pekarreferenser till samma minnesadress ändå alltid pekar på samma värde.

Datavyn är dock inte bara för att enkelspårigt visa displayer för olika data, det är även möjligt att manipulera dem. T ex går det att flytta runt och ordna displayer i datavyn med musen. Du markerar en display, eller ett deluttryck i en display, genom att klicka på den. Du kan markera flera samtidigt genom att hålla inne shift medan du klickar på flera, eller genom att med musen dra upp en ruta över önskade displayer i datavyn. Med Show ()-knappen kan du expandera ett gömt sammansatt data (dessa ser annars ut så här: {...}). Är datat redan expanderat finns en Hide ()-knapp som åter gömmer det. Displayer över arrayer kan roteras med Rotate (), dvs orienteras horisontellt respektive vertikalt. Med Set () kan du i vissa fall ändra värdet på en variabel. Efter att du klickat på Set () kommer en dialogruta upp där du kan skriva in det nya värdet. Delete () tar bort en display. Om du inte vill ta bort en display, men inte behöver dess värde på ett tag, kan du deaktivera den med Disable (). Den finns nu kvar i datavyn men uppdateras inte förrän den återaktiveras med Enable (). Att deaktivera displayer sparar tid, fast inte i lika hög grad som att ta bort dem helt.

Det var i korta ordalag vad DDD handlar om. För att lika kort beskriva GDB så är följande kommandon de viktigaste och mest grundläggande kommandona:

b function sätter en breakpoint vid 'funktion'
run [arglist] startar exekvering med eller utan argument
bt backtrace, visar stacken, vilken funktion som anropat vilken
p expr skriver ut värdet av uttrycket 'expr'
c continue, fortsätter exekvera efter en breakpoint
n next line, stega vidare en rad, stegar över funktionsanrop
s step line, stega vidare en rad, stegar ned i funktionsanrop
quit avsluta GDB

Räcker inte dessa kan du helt enkelt skriva help. Du får då upp en lista med ämnen. help ämne ger i sin tur en lista av kommandon och help kommando ger en beskrivning av aktuellt kommando.

GDBs kommandoradsgränssnitt är långt ifrån dåligt. Alla kommandon kan t ex förkortas, så länge förkortningen är entydig. Ett enkelt sätt att testa vilket kommando en förkortning är associerad med är att ge den som argument till help. Ett annat är att trycka på tab, antingen expanderas det tecken du skrev till motsvarande kommando, eller också visas de olika alternativen. De flesta kommandon går att upprepa genom att bara trycka return. En del, t ex step, kan ges en siffra för önskat antal upprepningar som argument.

GDB-kommandot list är en smula lustigt. Det listar tio rader källkod, med aktuell kodrad i mitten. Dock, om du skriver list igen listas istället de därefter påföljande tio raderna. Med upprepade list kan du alltså titta igenom hela din källkod.

Ett annat nyttigt kommando är info som beskriver aktuell status för ditt program. Du kan t ex lista aktuell funktions argument med info args eller se aktuella registervärden med info registers. Är du osäker på vilka breakpoints du satt ut kan du lista dem med info breakpoints. Fler info-kategorier hittar du med help info.

Ett tredje nyttigt kommando är watch expr, som sätter en watchpoint på uttrycket expr. Så fort som expr förändras stoppas exekveringen, perfekt om du vet att en variabel alltid plötsligt får ett felaktigt värde men inte var, eftersom den sätts lite överallt i din kod. Kruxet med watchpoints är att uttrycket måste kontrolleras hela tiden, vilket gör watchpoints mycket långsammare än breakpoints. Sålunda ska du inte använda watchpoints i onödan.

Nedan följer ett enkelt, felfritt program du kan använda för att testa GDB/DDD. Du kan t ex kontrollera vilket värde indexvariabeln i har efter for-loopen, men före exit. Vad tror du den ska vara? Enligt definitionen av C kan du inte lita på att i ska ha det logiska värdet. Efter en loop kan indexvariabeln istället vara odefinierad. Är den det på den datorn du sitter på? Är den det på någon av cs datorer? Testa att kompilera utan -g och sedan debugga med GDB/DDD. Vad händer? Varför? Testa även att vid promten slå ett unsetenv USER och sedan köra/debugga loopindex. Vad händer? Varför?

/*
 * Compile with gcc -g -Wall -o loopindex loopindex.c
 */

#include <stdio.h>  /* printf */
#include <stdlib.h> /* getenv */
 
int main()
{
  int  i;
  char *user;
 
  if ((user = getenv("USER")) == NULL) {
    printf("You haven't got $USER set!\n");
    exit(1);
  }
  
  for (i = 0; i < 10; i++)
    printf("Hello %s!\n", user);

  exit(0);
}

Mer information hittar du med ginfo gdb, man ddd och på http://www.cs.tu-bs.de/softech/ddd/.

GNU Make och Mkmf

Make är det svåraste programmet i paketet att beskriva. De andra är program av en viss typ: en editor, en debugger, etc. Det finns dock inget bra namn för den sorts program Make är. Make är Make. Vad gör då Make? Make administrerar uppdateringen av en samling filer. Vanligtvis används Make för att förenkla kompileringen av ett program. Istället för att kompilera varje modul för sig när de ändrats, och sedan sätta ihop dem, sköter Make automatiskt uppdateringen. Generellt sett kan Make användas till vad som helst, så länge det finns beroenden mellan de olika delarna. Den här handledningen skulle t ex kunna bestå av en fil för varje program som jag editerar var för sig och sedan slår ett enkelt Make när jag vill uppdatera det färdiga dokumentet. Mycket bättre än att behöva sätta ihop delarna för hand, även om det bara rör sig om ett kommando per del. Somliga menar att Make ska användas så fort en uppgift kräver två eller fler kommandon. Detta är dock lite överdrivet. Make ska användas för att förenkla upprepade sekvenser av kommandon som det är lätt att göra fel på om man kör dem för hand. Sedan finns det ju entusiaster som överanvänder Make en smula. Det värsta jag sett är en laboration där Make kompilerade programmet, körde testkörningarna, gjorde grafer av utvärdena och skrev ut en komplett labbrapport. Studenten ifråga önskade bara att han automatiskt kunde genererat tolkningar av graferna och värdena också - när han rättat en bugg blev utvärdena och graferna helt annorlunda men de gamla tolkningarna stod kvar i rapporten...

För att använda Make behöver du en makefile, en fil som beskriver hur de aktuella filerna beror av varandra och vilka kommandon som behövs för att uppdatera dem. När du har en korrekt makefile behöver du bara skriva gmake och trycka enter så sköter programmet om resten. Make avgör utifrån informationen från aktuell makefile och tidsuppgifterna för sista modifiering för övriga filer, nämnda i make-filen, om den ska utföra kommandot/kommandona associerade med respektive fil.

En makefile är uppbygd så här:

target ... : dependencies ...
        command
        ...
        ...

Observera det inledande tabbsteget på kommandoraderna ovan.

Ett target, ett mål, brukar vara namnet på den fil som genereras av kommandona som hör till regeln, men kan också vara speciella mål, såsom "all" eller "clean", som inte motsvaras av en fil med samma namn.

Ett dependency, ett beroende, är en fil, eller ett annat mål, som aktuellt mål beror av. Ofta används beroende-filerna som indata när målet skapas. Mål beror ofta av flera filer.

Ett command, ett kommando, är precis vad Make utför när aktuell regel är tillämpbar. Observera att kommandona måste skrivas ett tabbsteg in på respektive rad. Det fungerar inte med åtta blanksteg, det måste vara ett tabbsteg.

Första raden, som består av ett eller flera mål till vänster om kolonet och de filer dessa mål beror på till höger, utgör själva regeln. Om något av det målet beror av har förändrats så ska målet uppdateras. Detta innebär att målet självt markeras som ändrat och att efterföljande kommandon utförs. Om målet förekommer i högerledet på andra regler propageras ändringar på detta sätt uppåt i beroendeträdet.

Nedan följer ett exempel på en väldigt enkel makefile, som inte använder sig av några av Make:s finesser:

#
# This is a easy and educational example of a makefile
#
all : lab1a lab1b

lab1a : lab1a.o counter.o
        gcc -o lab1a lab1a.o counter.o

lab1b : lab1b.c
        gcc -o lab1b lab1b.c

lab1a.o : lab1a.c lab1a.h
        gcc -c lab1a.c

counter.o : counter.c lab1a.h
        gcc -c counter.c

clean :
        /bin/rm -f lab1a lab1b *.o

Lab1a består av två delar, lab1a.c och counter.c. De delar dessutom på en gemensam header-fil, lab1a.h. Om någon av dessa tre filer ändras måste lab1a kompileras om. Detta sköter nu Make genom att känna av vilken fil som ändrats, kompilera motsvarande objekt-fil och sedan länka ihop den nya objekt-filen för den förändrade delen med den oförändrade delens gamla objekt-fil. Om lab1a.h-filen ändras kompileras både lab1a.o och counter.o om, eftersom de båda beror på lab1a.h.

gcc -o lab1b lab1b.c direkt istället för att köra Make. Tag dock för vana att göra en makefile och köra gmake ändå. Om inte annat är ju gmake kortare än gcc -o lab1b lab1b.c. Det är dessutom enklare att lägga till och ta bort olika flaggor i en makefile, än genom att skriva dem på kommandoraden.

För att få uppdaterade versioner av både lab1a och lab1b kör du gmake all eller bara gmake. Anger du ingen regel uppdaterar Make den första regeln i din makefile. Vill du ha antingen lab1a eller lab1b kör du gmake lab1a respektive gmake lab1b. När du fått din laboration godkänd kör du gmake clean. Då raderas de körbara programmen och objekt-filerna. De är dessa som tar upp mycket plats, vilket du som student inte har obegränsade mängder av på ditt UNIX-konto. Kvar blir bara källkoden och din makefile. Behöver du programmet igen kör du bara gmake för att på nytt kompilera upp körbara program.

Lägg märke till att första regeln, "all"-regeln, inte utför några kommandon utan hoppar vidare till respektive regel för beroendena för att kontrollera om dessa, eller dess beroendens regler osv, behöver uppdateras. Först när alla beroenden som har egna regler besökts och dess beroenden i sin tur är kontrollerade, börjar Make utföra kommandona associerade med reglerna om det behövs. Först utförs kommandon för att uppdatera ändrade beroenden längst ned i hierarkin, sedan vandrar Make uppåt i trädet igen. Om "all"-regeln hade haft några kommandon skulle de vara de sista som utfördes, även fast "all"-regeln var den första som besöktes.

"clean"-regeln beror inte på någonting. Därför kommer Make aldrig att komma till den regeln om du inte explicit anger när du kör Make att det är "clean"-regeln som skall utföras.

Exemplet ovan var dock väl enkelt. Det begagnade sig inte alls av Make:s alla finesser. Så här borde Make-filen för lab1a ur exemplet ovan se ut:

#
# This is a pretty good template for a makefile, 
# at least for smaller projects
#
PROG	=	lab1a
OBJS    =       lab1a.o counter.o 
CC      =       gcc
CFLAGS  =       -g -Wall

all : $(PROG)

$(PROG) : $(OBJS)
        $(CC) $(CFLAGS) -o $(PROG) $(OBJS)

$(OBJS) : lab1a.h

clean :
        /bin/rm -f $(OBJS) $(PROG)

Skillnaden är att vi nu använder variabler i vår makefil. Fördelen är att vi bara behöver skriva reglerna en gång, sedan ändrar vi bara i variabel-tilldelningarna. T ex ändrar vi CFLAGS, flaggorna till kompilatorn, från debugg-flaggorna ovan till optimeringsväxeln -O när vi fått programmet att fungera som det ska.

När vi refererar till en variabel måste vi skriva den inom paranteser efter ett $.

Vidare utnyttjar vi Make:s inbyggda, implicita regler. Raden $(OBJS) : lab1a.h anger att alla objekt beror av header-filen lab1a.h, men vi har varken angett att de även beror av deras respektive .c-fil eller hur Make ska skapa .o-filerna från .c-filerna. Detta behöver vi dock inte göra. Make vet att när den stöter på en .o-fil som saknar regel för hur den skapas, så ska den titta efter en motsvarande .c-fil och med C-kompilatorn och optionerna till den samma, angivna i variablerna CC och CFLAGS, skapa korresponderande .o-fil.

CC och CFLAGS är standard variabler när det gäller kompilering av vanliga C-program. Om du istället skriver i C++ heter motsvarande variabler CXX och CXXFLAGS.

Skillnaden mellan explicita och implicita regler är att explicita regler anger när och hur man uppdaterar en eller flera filer medan implicita regler anger när och hur klasser av filer skall uppdateras, med utgångspunkt från filernas namn. Normalt skriver du explicita regler och använder dig av de implicita, men du kan även skriva egna implicita regler.

Vanligtvis skriver Make ut varje rad den kör, dvs det blir en räcka gcc ..., gcc .., osv. Detta är om inte annat bra för att kunna upptäcka om Make-filen är felaktig på något sätt. Ibland vill man kunna stänga av utskriften. Det gör man genom att skriva ett @-tecken före kommandot man vill ska utföras tyst. Glöm dock inte att det fortfarande ska vara ett tabbsteg först på raden, i detta fall före @-tecknet. Vill du stänga av all utskrift från Make kan du istället använda optionen -s (slient operation). make -s utför sålunda eventuella kommandon utan att skriva ut dem på skärmen.

En annan användbar option till Make är -n. Om du kör gmake -n gör Make den vanliga kontrollern av aktuella filers ålder, skriver ut de kommandon som reglerna säger ska utföras men utför dem inte. Den här växeln är till för att testa vad som skulle hända om du körde Make som vanligt, bra för att debugga make-filer.

Varje kommandorad i en regel utförs i ett eget subshell. Om två eller flera kommandon är beroende av att utföras i samma shell för att fungera, t ex cd myfiles och cat monkey.c skriver man dem på samma rad och separerar dem med ett semikolon: cd myfiles ; cat monkey.c. Görs inte detta hoppar cd:t ner i katalogen myfiles i ett eget subshell och returnerar sin exit-kod, varpå subshellet avslutas och ett nytt subshell startas där cat monkey.c exekveras. Tyvärr står vi åter i katalogen över myfiles/ i det nya shellet och cat misslyckas med att hitta filen monkey.c. En annan variant är att avsluta cd-raden med en backslash: cd myfiles \. Backslash-tecknet används för att binda samman olika påföljande rader till vad Make tror är en enda obruten rad. Främst använder man \ för att bryta rader, som är längre än textfönstrets bredd, snyggt och läsbart. (Naturligtvis skulle vi även kunnat slå ihop kommandona ovan till cat myfiles/monkey.c, men då skulle själva poängen med exemplet gå förlorad).

När ett kommando är utfört returneras exit-koden till Make som utifrån detta avgör om det ska fortsätta sin körning eller avbryta den. Har kommandot utförts som det ska kan nästa kommando utföras. Har det blivit något fel avbryter Make aktuell regel, vilket kan betydda att hela Make-körningen avbryts. Det finns dock fel som egentligen inte är några fel. T ex returnerar mkdir ett misslyckande om katalogen det försöker skapa redan finns, vilket ju inte spelar någon roll för Make, katalogen finns ju i så fall där den ska vara. Genom att lägga till ett minustecken mellan tabbsteget och kommandot på en kommandorad, förmår man Make att ignorera eventuella felmeddelanden från kommandot ifråga.

Make är ett tämligen avancerat program, det finns t ex en rad inbyggda funktioner som man kan anropa från sin makefile och det är t ex möjligt att köra Make rekursivt genom att anropa det i makefiles, men det är än så länge överkurs. På de flesta labbkurser är vad som tagits upp hittills mer än adekvat. Du får dock hemskt gärna hitta andra, egna användningsområden för Make.

För mer information, kör man gmake eller ginfo make.

Mkmf

Mkmf, MaKe a MakeFile, skapar automatiskt en makefile åt Make genom att gå igenom källkodsfilerna i aktuell katalog för att leta efter inkluderade filer och beroenden. Mkmf skapar dock ingen optimerad makefile, snarare genereras en enorm fil med massor av oanvända prylar. Detta gör att en mkmf-genererad makefile kan vara svår att förstå, särskilt för en ovan användare. Det är dessutom lätt att lura mkmf, t ex klarar det inte av att lägga till flaggor för inlänkning av nödvändiga bibliotek. Därför krävs det ofta för alla icke-triviala programmeringsprojekt att man själv går in och rättar till en mkmf-genererad, icke-fungerande, makefile. Detta är dock nästan omöjligt om man inte har någon tidigare vana av make-filer. Mitt råd är därför att du skriver dina egna make-filer i början så att du lär dig hur de fungerar. Du kan även testa att döpa om din egen, kör mkmf och jämföra din makefile med mkmf:s. Ser du att de egentligen fungerar på samma sätt, fast den mkmf-genererade är så mycket större och grötigare?

För mer information, kör man mkmf.

GNU Enscript

GNU Enscript konverterar textfiler till PostScript. Det främsta ändamålet med Enscript är att skriva ut textfiler (t ex källkod, mail, etc) till en PostScriptskrivare, andra användningsområden är som skrivarfilter eller mycket svåranvänd ordbehandlare. En fördel med Enscript är att det kan känna igen och markera olika syntaktiska delar i en fil den skriver ut. Detta är bra för t ex källkod, där bland annat kommentarer, funktions- och variabeldeklaration markeras på ett speciellt sätt så att koden blir lättare att läsa. På samma sätt finns det stöd för utskrift av mail, news-artiklar och html-kod. GNU Enscript har även en rad andra finesser som t ex möjligheten att lägga in eps-bilder eller ren poscriptkod i den fil du vill skriva ut. Dessa finesser används dock sällan.

Om du bara skriver genscript filnamn skrivs textfilen ut på den skrivare som är associerad med datorn du exekverade Enscript ifrån. För maskinerna i UNIX-labben är detta labbskrivarna, men kör du Enscript från en dator någon annanstans, t ex om du sitter i ett PC-labb och är inloggad på någon UNIX-maskin, kan du styra vilken skrivare du vill att din fil ska skrivas ut på genom växeln -P skrivarnamn, exempelvis genscript -Pma316ps filnamn. Andra optioner som det är bra att känna till är:

-2

Ger två kolumner text på en sida, vill man ha fler får man använda --columns=antal
-a sidor



Styr vilka sidor du vill skriva ut, du kan ange ett slutet intervall: -a startsida-slutsida, ett öppet intervall: -a startsida- eller -a -slutsida, en enda sida: -a sidnummer. -a odd och -a even skriver ut alla udda respektive jämna sidor.
-E språk


Skriver ut din fil med språkspecifika markeringar. Om inget språk anges gör Enscript en kvalificerad gissning. Du kan se vilka format som Enscript stödjer genom att slå genscript --help-pretty-print.
-G Skriver ut ett snyggare sidhuvud.
-n antal

Skriver ut ett antal kopior av din fil. Ex: genscript -n3 filnamn skriver ut 3 kopior.
-p filnamn


Skriver ut din fil som PostScript-kod till det filnamn du anger. Ex: genscript -p kalle.ps filnamn skriver ut din fil till filen kalle.ps. Ger du filnamnet - skriver Enscript ut till STDOUT.
-r Skriver ut till liggande A4, mycket användbart tillsammans med -2.
-U num Skriver ut num logiska sidor på varje resulterande sida.

När du skriver ut källkoden till din labbrapport använder du lämpligtvis genscript -G2rE filnamn för att få två kolumner på liggande A4 med snygga sidhuvuden och syntax-markering. När du skriver ut kod för eget bruk kan du även lägga till -U2 för att rymma fyra sidor kod per utskriven sida. Detta sparar utskrifter för dig men brukar bli lite för smått för labbrättare att läsa.

Mer information hittar du med man genscript och på http://www.iki.fi/~mtr/genscript/.

Ordlista
core svenska: kärna, i detta fall den fil operativsystemet dumpar ett programs aktuella minnesstatus i när det kraschar
arbetsmiljö en uppsättning program och applikationer avsedda för arbete med en specifik uppgift, den "omgivning" du rör dig i "på skärmen"
editor program för att editera textfiler, saknar de funktioner för textformatering som utmärker en ordbehandlare
explicit uttryckligen, t ex du måste explicit be om påfyllning för att serveras en portion till
implicit underförstått, underförstått, t ex om inga askfat är utställda är det implicit att rökning undanbedes
konvention allmänt vedertagen regel, det är konvention att hålla gaffeln i vänster hand och kniven i höger
kärna engelska: kernel, själva "motorn" som driver operativsystemet på en dator
macro en "genväg", genom att skriva macron med lätta namn för långa, krångliga sekvenser av kommandon underlättas användningen betydligt
parse tolka, när en dator/kompilator parsar källkoden både tyder den instruktionerna och kontrollerar att programmet är korrekt
postscript ett sidbeskrivningsspråk. Idag är de flesta skrivare postscriptskrivare och postscriptfiler kan skickas direkt till den för utskrift
STDOUT STandarD OUT, standard pipan för utdata, vanligtvis det textfönster du arbetar i
[vi Powered] [TORBEN] http://www.cs.umu.se/information/misc/sfw-env.unix.html
Last modified 12 Sep 1999 by Selander