Lathund Pacific C för MS-DOS Revision 2 2003-08-28 Anders Arvidsson Karl-Johan Krantz Synpunkter välkomnas!
Innehållsförteckning 1 Introduktion...3 1.1 Assembler kontra C...3 1.2 Kodexempel...3 1.3 PACIFIC C...4 1.3.1 Installation...4 1.3.2 En första kompilering...5 2 ANSI-C...6 2.1 Flödeskontroll...6 2.1.1 Satser och block...6 2.1.2 Slingor...7 2.1.3 Villkor...9 2.2 Typer, operatorer och uttryck...11 2.2.1 Deklarationer av variabler...11 2.2.2 Matriser...12 2.2.3 Sizeof...12 2.2.4 Talrepresentation...12 2.2.5 Matematiska operatorer...13 2.2.6 Bitvisa operatorer...13 2.2.7 Logiska operatorer...14 2.2.8 Relationsoperatorer...14 3 Programstruktur...15 3.1 Preprocessor...15 3.2 Blanda C och assembler...15 3.3 Funktioner...16 4 In- och utmatning...17 4.1 Formaterad inmatning med scanf()...17 4.2 Formaterad utmatning med printf()...18 2
1 Introduktion Detta dokument beskriver grunderna i ANSI-C. Vidare beskrivs hur assembler och C kan kombineras för att på ett effektivare sätt utföra vissa uppgifter. Verktyget som används är Pacific C för MS-DOS. 1.1 Assembler kontra C Några argument för C: Avlusningen går betydligt snabbare i C, då en rad assembler tar lika lång tid att avlusa som en rad C, men C-koden är betydligt kortare. C ger möjligheter att återanvända kod i större utsträckning. C ger betydligt större möjlighet att strukturera sitt program. C är ett standardiserat sätt att skriva program, vilket ger möjlighet att flytta koden mellan olika processorer. Några argument för assembler: Assembler ger bättre kontroll över exekveringstider. Att skriva i C gör att man är utlämnad till kompilatorns sätt att göra assemblerkoden. Vid en kompilatorbugg blir det därmed svårt att komma vidare. Som regel kommer C-koden att kräva större programminne beroende på kompilatorns svagheter. Genom att kombinera C och assembler blir det möjligt att dra nytta av fördelarna med båda språken. 1.2 Kodexempel Samma funktion skriven med olika språk: C-kod void main(void) if (LEFT == RIGHT) MOTORHASTIGHET = 0xFF; else MOTORHASTIGHET = 0; while(1) continue; Assembler MAIN: MOV AL,LEFT CMP AL,RIGHT ;Jämför LEFT med RIGHT JE PLATTAN MOV AL,#0 MOV MOTORHASTIGHET,AL JMP SLUT PLATTAN: MOV AL,#0FFh MOV MOTORHASTIGHET,AL SLUT: JMP SLUT 3
1.3 PACIFIC C Pacific C är en integrerad utvecklingsmiljö för C-programmering under MS-DOS. Den innefattar bl.a. en editor, kompilator och debugger. Utvecklingsmiljön i Pacific C 1.3.1 Installation Skapa en ny mapp G:\Pacific på ditt eget konto. Hämta filerna K:\Data\Årskurs2\PC- Teknik\Programvaror\Pacific_C\pacific.exe och pacific_start.bat och lägg dem i den nyss skapade mappen. Kör pacific.exe för att packa upp nödvändiga filer. 4
1.3.2 En första kompilering Kör bat-filen pacific_start.bat för att starta utvecklingsmiljön. (Lägg gärna en genväg på skrivbordet till denna bat-fil.) Välj <File><New> för att skapa en ny fil. Spara filen under lämpligt namn (t.ex. hello.c). Skriv in ett enkelt program : #include <stdio.h> void main(void) printf( Hello World\n ); Välj <Compile><Compile and link> för att kompilera och länka din kod. På formulären fyller du i enligt nedan: Välj <Run><Run Proj1.exe> för att se resultatet. 5
2 ANSI-C ANSI-C är en standard som gör det möjligt att flytta program mellan olika miljöer och kompilatorer. Då språket saknar stöd för objektorientering och är relativt begränsat går det relativt snabbt att lära sig behärska, men genom att inte utnyttja alla möjliga skrivsätt, som i denna lathund, blir det ännu enklare. C++ innefattar ANSI-C som en delmängd, men är därutöver påbyggd med en mängd funktioner som inte behöver vara flyttbara mellan olika kompilatorer. Det finns dock idag en flyttbar ANSI C++. Största fördelen med C gentemot C++ är som regel att C ligger nära hårdvaran och är lätt att kompilera till effektiv kod som inte kräver så mycket minne och går bra att köra i inbyggda system (som mobiltelefoner, mikrovågsugnar eller videoapparater). Nedan följer en kort genomgång av oftast använda delar av ANSI-C. Detta läsas gärna i anslutning till ett större kodexempel. 2.1 Flödeskontroll 2.1.1 Satser och block Sats Syntax Block Syntax Kommentar En sats avslutas med ett semikolon. Flera satser kan knytas samman till ett block, vilket syntaktiskt är lika med en enda sats. 6
2.1.2 Slingor For Syntax for (startvärde; villkor; steguttryck) Exempel for (x=0; x<10; x++) Led ^= 1; //Blinka lysdiod Delay(); Kommentar For-satsen utförs så länge villkor är uppfyllt. Var och en av parametrarna kan utelämnas, men semikolonen måste vara kvar. While Syntax while (villkor) Exempel while (1) Led ^= 1; Delay(); //Blinka lysdiod för evigt Kommentar While-satsen utförs så länge villkor är uppfyllt, eller mer precist, så länge uttrycket i parentesen inte är = 0. 7
Do - while Syntax do while (villkor) Exempel do Led ^= 1; Delay(); while (!knapp) //Blinka lysdiod tills knapp trycks Kommentar Satsen utförs minst en gång, eftersom villkoret testas först efter en körning. Break Syntax break; Exempel while (1) Led ^= 1; Delay(); if (knapp) break; //Blinka lysdiod för evigt // såvida inte knappen trycks Kommentar Break orsakar en direkt avslutning av do, for, while och switch. 8
2.1.3 Villkor If - else Syntax if (villkor) else if (villkor) else if (villkor) else Exempel if (10 == x) Led ^= 1; //Toggla lysdiod när x = 10 Kommentar Else-satserna utförs endast när det egna villkoret uppfylls, men inget av ovanstående. Varje else-if bör avslutas med en else som körs om inget villkor uppfylls. Denna konstruktion används även när minst ett villkor alltid förväntas uppfyllas, detta för att indikera fel. Det felaktiga villkoret i if (x = 10) skulle vara svårt att finna, då det uppfyller sig självt. Genom att vända på uttrycket kommer kompilatorn att indikera det syntaktiska felet. 9
Switch Syntax switch (uttryck) case konstantuttryck: satser; case konstantuttryck: satser; default: satser; Exempel switch (CycCnt) case 0: K(); S(); break; case 1: case 2: K(); break; // Var 10:e ms // 0 ms // Krocksensor // Styr robot // 10, 20 ms default: CycCnt = 0; beak; Kommentar Heltalet i uttryck avgör till vilken etikett programmet hoppar (= konstanten efter case). Break behövs för att förhindra exekvering av efterföljande satser. Programmet hoppar till default om inget annat fall uppfylls. Ordningen mellan de olika fallen spelar ingen roll. 10
2.2 Typer, operatorer och uttryck 2.2.1 Deklarationer av variabler I Pacific C kan följande variabeltyper deklareras. Typ Storlek i bitar int 16 char 8 short 16 long 32 float 32 double 64 Variabler som deklareras utan signed eller unsigned tolkas automatiskt som signed. Deklarationer kan göras flera sätt. Vi kommer att behandla: global, lokal och statisk och konstant. Global: Deklarationen görs utanför en funktion, i början av programmet. Variabeln kommer därmed att bli tillgänglig för alla funktioner. Nackdelen är att variabeln tar upp plats även då den inte används. Lokal: Deklareras inuti funktionen. Dessa variabler är bara tillgängliga i den funktion de deklareras i, vilket innebär att minnesutrymmet kan återanvändas. Statisk: Om variabeltypen föregås av static kommer denna att beredas en statisk plats i minnet redan vid kompileringen, oavsett variabeltyp. Detta innebär t ex att variabeln kommer behålla sitt värde mellan funktionsanropen, även om den är lokal. Konstant: Om variableltypen föregås av const talar detta om för kompilatorn att värdet aldrig kommer att ändras. När const används måste tilldelning ske redan vid deklaration. 11
2.2.2 Matriser Vid deklaration av matriser används matrisindex []. Exempel på 2-dimensionell matris med 2 rader och 3 kolumner: const char demo[2][3] = 1, 2, 3 4, 5, 6 ; Första elementet har index 0. Exempel: tilldela variabeln x värdet 6 (rad 2, kolumn 3) x = demo[1][2]; Ofta är det praktiskt att använda endimensionella matriser för att beskriva bit-mönster eller look-up tabeller. Exempel: En rinnande ljus skapas med fyra pinnar på PORT B const char pattern[4] = 0b00000001, 0b00000010, 0b00000100, 0b00001000,; while (i < 5) PORTB = pattern[i]; Delay(); i++; 2.2.3 Sizeof För att ta reda på en variabeltyps eller variabels storlek kan instruktionen sizeof användas. Den ger storleken på typen eller variabeln i antalet bytes (1 byte = 8 bitar). char a[10]; printf( %d, sizeof(char) ); printf( %d, sizeof(a) ); //Ger storleken på typen char //d.v.s 1 byte //Ger storleken på matrisen a //d.v.s 10 bytes 2.2.4 Talrepresentation Talrepresentation kan ske på följande sätt: Talbas Format Binär 0b följt av talet Ex 0b10100001 Decimal Skrivs på vanligt sätt Ex 123 Hexadecimal 0x följt av talet Ex 0x3E ASCII (Ingen talbas ) Tecknet skrivs inom Ex B (= 66 decimalt) 12
2.2.5 Matematiska operatorer Operator Förklaring + addition - subtraktion * multiplikation / division % modulus (returnerar resten) = större än variabel++ ökar variabel med 1 variabel-- minskar variabel med 1 Exempel: a = b % c; // a tilldelas resten vid division av heltalet b med heltalet c a++; // a = a + 1 2.2.6 Bitvisa operatorer Operator Förklaring ~variabel bitvis invers av variabel & bitvis AND bitvis OR ^ bitvis XOR << n skifta n steg vänster >> n skifta n steg höger Exempel: svar = (A >> 5) & 1; // Skiftar bit 4 till bit 0 och maskar sedan ut bit 0 Om A sätts till 0010001b kommer svar att bli 1. 13
2.2.7 Logiska operatorer I C räknas allt > 0 som SANT och 0 som FALSKT. Operator Förklaring! logisk invers && logisk AND logisk OR = = likhet!= olikhet 2.2.8 Relationsoperatorer Operator Förklaring > större än >= större än eller lika med < mindre än <= mindre än eller lika med <> inte lika med 14
3 Programstruktur 3.1 Preprocessor Genom att kunna ersätta värden, uttryck, tecken mm med en egen vald förklaring kan programmet göras mer lättläst. #define timervalue 217 När kompilatorn hittar timervalue i programkoden, kommer den att ersätt uttrycket med talet 217. Observera att rader som börjar med # utgör instruktioner till preprocessorn och därför inte avslutas med ;. 3.2 Blanda C och assembler Ibland går saker smidigare i assembler, som t ex rotera via carry, eller tidsfördröjningar via NOP. Det finns också tillfällen då timing eller kodstorlek kräver att vissa delar av koden skrivs i assembler. Det finns två sätt att direkt infoga assembler i C-kod, asm( ) och #asm, se nedan. char hextal; void main(void) asm(" mov _hextal,#25,byte"); // hextal tilldelas värdet 25 #asm nop nop nop #endasm // Start på assemblerblock // Genererar en fördröjning // Slut på assemblerblock För att assemblerkoden ska hitta hextal krävs att variabelnamnet föregås av ett _. Konstruktionen asm(" mov _hextal,#25,byte"); utgör C-syntax och fungerar bra varhelst det används. #asm är däremot inte någon C-kod och kan därför inte användas i alla situationer, t ex inne i en ifsats. 15
3.3 Funktioner Funktioner används för att få en bättre struktur och mer återanvändbar kod. En funktion kan ha inoch utparametrar, detta är dock inte nödvändigt. Hur funktioner skrivs visas med följande exempel. Programbeskrivning: Talen A och B skall multipliceras med varandra. Svaret skall sedan tilldelas variabeln C. Observera att variablerna A och B är char medan C är integer. char A, B; int C; int mult(char a, char b) // a,b är lokala variabler return a * b; // Multiplicerar talen a och b med varandra void main(void) A = 10; B = 212; C = mult(a, B); while(1) continue; // Continue = kör while-loopen igen. 16
4 In- och utmatning Nedan beskrivna funktioner ligger i biblioteket stdio.h. 4.1 Formaterad inmatning med scanf() Formaterad imatning sker med funktionen scanf(). Dess syntax är: scanf( %* %*, &var1, &var2); där * anger vad det är för typ av data som skall lagras i variablerna var1 och var2. (Scanf() kommer att spara inmatade värden på adresserna till variablerna, därav &-tecknen.) * kan vara något av följande: d decimalt heltal av typ int x hexadecimalt heltal av typ int (inmatning inleds inte med 0x) u decimalt heltal utan tecken av typ unsigned int ld, lu heltal av typ long int (signed resp. unsigned) c enkelt tecken av typ char. s teckensträng av typ char[] e, f, g flyttal med valbart tecken, decimalpunkt samt exponent ( ex. 1.234e-5 ) ex: #include <stdio.h> void main(void) int tal; char string[20]; scanf( %d %s, &tal, string); Observera att inget &-tecken används framför vektorn string. Detta för att ett vektornamn i sig är en pekare. Då man bara vill läsa in ett tecken från tangentbordet är det lätt att använda: scanf( %c, &tecken); Nackdelen är att entertryckningen vid inmatningen kommer ligga kvar tills nästa scanf() körs. Det är då bättre att använda följande syntax: scanf( %1s, &tecken); Detta gör att entertecknet kommer att hoppas över. 17
4.2 Formaterad utmatning med printf() Formaterad utmatning sker med funktionen printf(). Dess syntax är: printf( Text som skall skrivas ut, en variabel: %*, var); * anger önskad variabeltyp precis som hos scanf() tidigare. Observera dock att printf() i nuvarande version av Pacific C inte stöder flyttal. Kontrollsekvenser för att skriva ut vissa tecken: \n radmatning \t tab \\ backslash \ citationstecken ex: #include <stdio.h> void main(void) char name[20]; int age; printf( Skriv namn och ålder separerade med mellanslag\n ); scanf( %s %d, name, &age); printf( Hej %s, du är %d år gammal!\n, name, age); 18