C-programmering – Primitiva datatyper

Nu är det dags att gå igenom hantering och representation av data. Vare sig det handlar om instruktioner, heltal, flyttal eller tecken är all data i grund och botten en kombination av ettor och nollor i en serie, men helt och hållet beroende på var och hur den laddas eller sparas får den olika betydelser. Och det är dessa betydelser vi ska gå igenom nu.

Data som laddas av kontrollenheten är instruktioner.
Data som laddas och sparas av pipelinen är data.

Och det är data vi ska ägna oss åt nu.

Först och främst måste vi ha olika sätt att representera data på. Dels måste vi veta hur många bytes som ska laddas och sparas, och dels måste vi veta hur datan ska behandlas. Tidigare har vi pratat om två olika begrepp: Heltal och flyttal, dessa är två olika sätt att behandla data på. Heltal är precis som de låter, en serie med ettor och nollor som representerar ett matematiskt heltal, det vill säga 0, 1, 2, 3, … , n-1, n, n+1 och så vidare. Flyttal å sin sida innehåller en heltalsdel, men också en del som är en fraktion av ett heltal. Även flyttalen representeras av en serie med ettor och nollor.

Nu är frågan, hur lång är en serie med ettor och nollor? Svaret är att längden beror på vilken instruktion som väljs när källkoden kompileras. Det valet är i sin tur beroende av arkitektur och nyckelord i C. Nyckelorden mappar till datatyper som antingen kan vara primitiva eller användardefinierade, vi kommer att gå närmare in på användardefinierade datatyper inom kort, nu ska vi bara diskutera primitiva datatyper. Primitiva datatyper är i C en samling datatyper som är inbyggda i språket och aritmetiska, det vill säga att de antar värden som man kan utföra aritmetik på. De primitiva datatyperna är:

char – Ett heltal på minst 8 bitar som används för att lagra tecken, men kan likaväl lagra vanliga heltal. Värdemängden varierar med antalet bitar, men det allra vanligaste är 8 bitar, vilket gör att värdemängden hamnar på 256 heltal, alltså [-128, 127] för signed char, eller [0, 255] för unsigned char.

int – Ett heltal på minst 16 bitar, men vanligtvis 32 bitar. Detta är en implementationsspecifik fråga, för int som sådan är 32 bitar i alla 32-bitars och de flesta 64-bitarsimplementationer av C. Däremot är int 16 bitar i 16-bitarsimplementationen, och 64 bitar i Intels ILP64 där man definierat int32 för 32-bitars heltal. En som är 16 bitar i alla implementationer är short eller short int. long eller long int varierar mellan 32 och 64 bitar, och long long eller long long int är alltid 64 bitar. Säkraste sättet att ta reda på vad som gäller i ens egna system är att använda sizeof( )-operatorn med de olika datatyperna och dess tillägg som inparametrar. Då kan man skriva ut storleken. Det är viktigt att komma ihåg att sizeof( ) returnerar alla storlekar i bytes, där varje byte är 8 bitar lång. Det skulle innebära att storleken på en int varierar mellan 2 och 8 bytes.
Och på samma sätt som för char kan man använda signed och unsigned för int, och då garantera att talet antingen har ett tecken eller inte.

Här nedan följer de vanliga flyttalsrepresentationerna. Flyttal är till skillnad från heltal kapabla att lagra såväl heltal som fraktaler. Detta i utbyte mot att flyttalen måste lagras på en form som inte är direkt. Heltal kan lagras som de är skrivna i bitar, men ett flyttal måste omvandlas till en form där värdet kan räknas ut enligt (-1)sign bit2exp-bias1.significand. Sign bit utgör den mest signifikanta biten i flyttalet och kallas också för teckenbit, den är 0 för positiva tal och 1 för negativa tal. Exp utgör exponenten i talet lagrad i excess-format. Excess betyder helt enkelt att den är lagrad med bias, så att den minsta exponenten representeras av nollor och den största representeras av ettor, något som gör det lättare att jämföra exponenter. Significand är själva talet vars värde är normerat runt 1, eller 0 i ett specialfall. Ettan till vänster om binärpunkten behöver aldrig lagras, vi vet att det alltid är en etta förutom i ett av specialfallen som vi ska gå igenom strax. Det som lagras av signifikanden är fraktionsdelen.
Fyra specialfall förekommer baserat på olika värden i exp och significand-fälten. Om exponenten är maximal representeras den positiva respektive negativa oändligheten om significand == 0, om significand != 0 har vi NaN (not-a-number).
Om exponenten däremot är 0 är biten till vänster om binärpunkten 0, i det här specialfallet representeras 0 och mycket små tal som är mindre än det minsta normala talet. Vanliga flyttalsrepresentationer följer här nedan:

float – Ett flyttal på 32 bitar. Exponenten är här 8 bitar lång, bias är 127 och signifikanden lagras i 23 bitar. Minsta exponenten är -126 (1) och största är 127 (254). 255 är reserverat för oändligheten och 0 innebär att exponenten är -126 och signifikandens mest signifikanta bit (biten till vänster om binärpunkten som aldrig behöver lagras) är 0.

double – Ett flyttal på 64 bitar. Exponenten är här 11 bitar lång, bias är 1023 och signifikanden lagras i 52 bitar. Minsta exponenten är -1022 (1) och största är 1023 (2046). 2047 är reserverat för oändligheten och 0 innebär att exponenten är -1022 och signifikandens mest signifikanta bit (biten till vänster om binärpunkten som aldrig behöver lagras) är 0.

long double – Ett flyttal som i vissa implementationer används för flyttal med större precision än double. Den har 80 bitar varav 15 utgör exponentbitar, bias är 16383 och signifikanden lagras i 64 bitar. Till skillnad från float och double har long double explicit lagring av signifikandens mest signifikanta bit, vilket kan anses vara fördelaktigt i fråga om snabbhet. Detta format implementeras främst i x86-baserade arkitekturer.

Så för att sammanfatta det hela är ordlängden avgörande för värdemängden på ett heltal och värdemängden samt precisionen på ett flyttal. Härnäst ska vi titta på ett par viktiga användningsområden för datatyper, nämligen variabler och pekare, vilket kommer i nästa inlägg.

Peace!

C-programmering – Hello World!

Vi är nu redo att ge oss på ett första program, och det ska vi naturligtvis göra med det klassiska ”Hello World”-exemplet. Programmet gör ingenting annat än att skriva ut strängen ”Hello World!”, men det finns en hel del information som kan vara bra att gå igenom innan.

Vårt program är en simpel applikation som ska kunna anropas från bash, terminalen eller kommandotolken. Därför måste den innehålla funktionen main( ) där utskriften sker antingen direkt i funktionen eller genom funktionsanrop till någon annan funktion. Funktionen main( ) markerar startpunkten i ett program alltså den första adress som datorn ska hämta en instruktion från. main( ) kan deklareras på flera sätt:

int main( ) innebär att main( ) ska returnera ett heltal av typen int, men har inga inparametrar.
int main(int argc, char *argv[ ]) innebär att main( ) returnerar ett heltal av typen in, och får sina inparametrar från anropskommandot. Inparametern kan vara ett heltal eller en sträng.
void main( ) är väldigt ofta en förbjuden deklaration eftersom systemet som anropar main( ) kräver att funktionen returnerar en nolla för att meddela att programexekutionen har genomförts problemfritt, vilket void inte gör. Dock finns det kompilatorer och system som teoretiskt sett tillåter void som returtyp från main( ). Tumregeln är att använda int main( ) eller int main(int argc, char *argv[ ]).

Utskriften gör vi genom att skicka strängen till stdout-streamen som programmet skickar data till genom speciella biblioteksfunktioner. Den funktion vi använder heter printf( ) och den finns i stdio.h. Därför måste stdio.h inkluderas som ett preprocessordirektiv.

Programmet i sin helhet ser ut så här:

#include <stdio.h>

int main( )
{
printf(”Hello World!”);
return 0;
}

Utskriften blir precis som väntat:
Hello World!

Peace!

 

C-programmering – Operatorer och Symboler

Egentligen har vi redan gått igenom tillräckligt mycket för att kunna skriva ett första program, men vi ska inte göra det riktigt än. Vi ska först gå igenom alla olika symboler och operatorer som kan förekomma i ett program. En del av symbolerna har vi redan gått igenom, vi kommer att gå igenom dem en gång till lite mer i detalj.

# – Preprocessordirektiv
Preprocessordirektiv används för att ge speciella anvisningar till kompilatorn. Dessa gör att preprocessorn raderar eller lägger till kod av olika anledningar. Dessa anledningar kan vara att koden inte behövs, eller så behövs koden. Preprocessordirektiv anges med #, precis som vi sa i avsnittet om kompilering. Några vanliga direktiv anges nedan:

#include <header.h> eller #include ”header.h” – Säger åt preprocessorn att inkludera innehållet i .h-filen med angivet namn. I dessa .h-filer finns speciella funktioner och definitioner som är av nytta i programmet. Om .h-filen är en systemheader måste man skriva dess namn innanför < >-parenteser. Om det är en lokal .h-fil använder man citattecken, alltså ””.

#define symbolnamn värde eller #define symbolnamn(p0, p1, … , pn) uttryck – Definierar speciella makron som kan vara konstanter eller funktioner. Preprocessorn ersätter symbolnamnet med det angivna värdet, om det finns något. Det går att definiera tomma makron endast för definitionens skull. Om makrodefinitionen är en funktion måste makroanropet göras tillsammans med parametrar, då returneras resultatet av funktionen.

#undef symbolnamn – Tar bort en makrodefinition.

#ifdef symbolnamn – Säger åt preprocessorn att underliggande kodrader ska kompileras om och endast om angivet makro är definierat.

#ifndef symbolnamn – Säger åt preprocessorn att underliggande kodrader ska kompileras om och endast om angivet makro ej är definierat.

#if uttryck eller #if defined symbolnamn – Säger åt preprocessorn att underliggande kodrader ska kompileras om och endast om angivet uttryck stämmer. I det här fallet handlar det alltså om heltalskonstanter, teckenkonstanter, logiska operatorer och aritmetiska operatorer. Makron måste expanderas innan preprocessorn kan testa om givet uttryck stämmer. #if kan också användas för att testa om ett makro är definierat, detta görs genom att sätta defined efter if. Det uttrycket får då samma betydelse som #ifdef.

#else if uttryck, #elsif uttryck, #elif uttryck – Säger åt preprocessorn att underliggande kodrader ska kompileras om och endast om angivet uttryck stämmer, men inte det första. Detta är alltså ett alternativ till #if och kan aldrig användas utan #if.

#else uttryck – Säger åt preprocessorn att underliggande kodrader ska kompileras om och endast om angivet uttryck stämmer, men inte de föreliggande. Detta är alltså det sista alternativet i en if-else-sats.

#endif – Säger åt preprocessorn att avsluta en #if-else-sats.

#pragma – Säger åt preprocessorn att ge direktiv till kompilatorn som är maskinspecifika eller systemspecifika. Det är alltså ingenting som finns i själva språket. Vanliga pragman är once, som säger att en header bara ska användas en gång oavsett hur många gånger den importeras; och config, som är ett direktiv i PIC-programmering för att konfigurera PIC-processorn för önskade funktioner. PIC-programmering är en form av maskinnära programmering som vi ska gå igenom senare.

# och ## har särskilda funktioner vid utskrifter av textsträngar. # framför en parameter till ett makro konverterar parametern till en sträng som kan skrivas ut. ## sammanför två parametrar till en. Det kan exempelvis vara två inparametrar till en makrofunktion, men det går lika bra med operatorer så som + och =, så att de tillsammans formar +=.

Fler preprocessordirektiv kommer att förklaras vid behov.

Parenteser
Parenteser är viktiga avskiljare i C-programmering. De har olika funktioner beroende på vilka och hur de används. Här nedan går vi igenom dem:

Vanliga parenteser, ( ), har flera viktiga användningsområden.
– Avskilja uttryck vid behov, exempelvis när någonting ska prioriteras framför det andra.
– Isolera villkor.
– Indikation av funktionsanrop och inparametrar.
– Typecasting, som innebär att datatyper omvandlas till andra.

Hakparenteser, [ ], används för att indexera arrayer, ett par per index.

Klammerparenteser, { }, eller måsvingar, används för att skilja kodblock och funktioner. Klammerparenteserna utgör viktiga avskiljare för hierarkier, på så vis att allt som deklareras utanför ett avskilt område får en global definition för avskilda block på samma nivå inom samma område. Exempelvis om vi säger att funktion 0 innehåller de avskilda blocken 1 och 2, och block 1 innehåller de avskilda blocken 3 och 4, och block 2 innehåller de avskilda blocken 5 och 6. Vidare antar vi att variabel x är global, det vill säga deklarerad utanför 0, variabel y är deklarerad i 0 och variabel z är deklarerad i 1. Det som då händer är att man har tillgång till x var som helst i programmet, man skulle kunna skriva en funktion till avskild från 0 och fortfarande ha tillgång till x. y däremot är deklarerad i 0, den andra funktionen skulle inte ha tillgång till den variabeln, däremot har alla block i 0 full tillgång till y. z är deklarerad i 1, det innebär att 2 inte har någon tillgång till den.

Satsavslut och uttrycksseparering
Satser och uttryck kan i vissa fall avskiljas med hjälp av kommatecken ( , ). Exempelvis om man vill skilja på inparametrar till en funktion som har flera inparametrar, görs detta med kommatecken. De kan också användas för att avskilja flera variabler som man deklarerar av en och samma typ.
Semikolon ( ; ) avslutar alltid en sats.

Etiketter för flervalsuttryck
Separabla fall i ett switch-case-uttryck presenteras med ett vanligt kolon ( : ).

Medlemmar i sammansatta datatyper
Medlemmar i en så kallad struktur kan man komma åt på två olika sätt, antingen genom en punkt ( . ) eller genom en högerpil ( -> ). Punkten används i variabler, medan högerpilen används i peklare. Medlemmarna kommer man åt genom strukturnamn.medlem eller strukturnamn->medlem.

Strängar och tecken
Strängar och tecken anges innanför citationstecken ( ‘ ‘ och ” ” ). Detta är skillnaden mellan om en variabel ska behandlas som en variabel eller som ett tecken/sträng i utskriftssammanhang.

Kommentarer
// Det här är en enradskommentar
/*
Det här är en
flerradskommentar
*/

Variabler, pekare och adresser
Variabler och pekare deklarerar man genom att ange datatyp namn1 respektive datatyp *namn2. Adressen till namn1 får vi genom att ange &namn1, och genom att tillskriva namn2 denna adress gör vi så att namn2 pekar på namn1.

Operatorer
Anta att a, b och c är heltalsvariabler av samma typ, då gäller det att:

c = a + b  ( c tillskrivs summan av a och b )
c = a – b  ( c tillskrivs differensen mellan a och b )
c = a * b  ( c tillskrivs produkten mellan a och b )
c = a / b  ( c tillskrivs kvoten mellan a och b )
c = a & b  ( c tillskrivs den bitvisa and-operationen mellan a och b, alltså bit för bit )
c = a | b  ( c tillskrivs den bitvisa or-operationen mellan a och b )
c = a ^ b  ( c tillskrivs den bitvisa xor-operationen mellan a och b )
c = ~a  ( c tillskrivs den bitvisa negeringen av bitarna i a )
c = -a  ( c tillskrivs tvåkomplementet till a )
a++  ( a inkrementeras, alltså identiskt med a = a + 1 )
a–  ( a dekrementeras, alltså identiskt med a = a – 1 )
c = a && b  ( c tillskrivs den logiska and-operationen mellan a och b, 1 om a och b är ”sanna” och 0 om a är ”falsk”. )
c = a || b  ( c tillskrivs den logiska or-operationen mellan a och b, 1 om a och/eller b är ”sanna” och 0 om båda är ”falska” )
c = !a  ( c tillskrivs den logiska negeringen av a, 0 om a är ”sann” och 1 om a är ”falsk” )
c = a % b  ( c tillskrivs resten av heltalsdivisionen mellan a och b )
c = a << b  ( c tillskrivs a som vänsterskiftats b steg bitvis )
c = a >> b  ( c tillskrivs a som högerskiftats b steg bitvis )
a == b  ( returnerar 1 om a är identisk med b, annars 0 )
a != b  ( returnerar 1 om a är skild från b, annars 0 )
a < b  ( returnerar 1 om a är strikt mindre än b, annars 0 )
a > b  ( returnerar 1 om a är strikt större än b, annars 0 )
a <= b  ( returnerar 1 om a är mindre än eller lika med b, annars 0 )
a >= b  ( returnerar 1 om a är större än eller lika med b, annars 0 )

Talsystem
För att skilja mellan decimala, hexadecimala, oktala och binära värden i C sätter vi följande prefix framför talen:
– Ett tal utan något prefix är decimalt. Exempel: 7223.
– Ett tal med prefixet 0b är binärt. Exempel: 0b01110110.
– Ett tal med prefixet 0 är oktalt. Exempel: 023.
– Ett tal med prefixet 0x är hexadecimalt. Exempel: 0x3a4e.

Peace!

C-programmering – Tal och Baser

De två första inläggen har ägnats åt att beskriva C som programspråk och hur kompileringsprocessen går till. Nu är det dags för tal i olika baser. Normalt lär vi oss i skolan att räkna med basen 10, det vill säga att vi representerar varje tal med siffrorna 0-9. En dator har dock inte förmågan att förstå tal på samma sätt.

En dator kan endast förstå siffrorna 0 och 1.

Gottfried Wilhelm von Leibniz var först med att utforma det binära talsystemet, men han fick så klart sina idéer från hexagrammen i 1 Ching, som var ett gammalt kinesiskt system med sex horisontella linjer. Varje linje var antingen hel eller delad, och mönstret på linjernas form var mycket likt det binära talsystemet. Vidareutveckling av det binära talsystemet med ettor och nollor gav upphov till boolesk algebra, utformad av George Boole, och det är grunden till all modern digital elektronik. Och eftersom all elektronik, såväl i datorer som i övrigt, bygger på boolesk algebra, måste programmeraren vara medveten om hur boolesk algebra fungerar. Därför måste också programmeraren vara införstådd i det binära talsystemet.

Det talsystem som vi lär oss att räkna med i skolan är decimalt, det finns alltså 10 möjliga sätt att representera ett tal innan man behöver fler siffror. 0-9 används för att representera de första 10 talen och så fort man adderar 1 till 9, alltså 9+1, behövs två siffror för att representera ett tal. Talet 10 representeras helt enkelt av 10. I det binära talsystemet representeras de 2 första talen med 0 och 1, för att representera 2 behövs helt plötsligt 2 siffror, eller bitar som de kallas. För om 1 representerar det högsta möjliga värdet på en bit måste 1+1 rimligtvis bli 10. Man inser direkt att 3 representeras av 11 och 4 (11 + 1) kräver 3 bitar, det vill säga 100. Beloppet på en siffra i ett tal är produkten mellan siffrans enskilda värde och basens potensvärde:

0001 = 1×20 = 1
0010 = 1×21 = 2
0100 = 1×22 = 4
1000 = 1×23 = 8

För det decimala talsystemet blir uträkningarna precis de samma, fast med 10 istället för 2 i basen. Produkterna blir därför 1, 10, 100, 1000 etc.

Nu kan långa bitsträngar bli väldigt bökiga att gå igenom, därför kan man också använda sig av det hexadecimala talsystemet. Det hexadecimala talsystemet använder 16 som bas och därför finns det 16 siffror, eller hexadecimaler, för att representera värden med. Hexadecimalerna representeras med 0-9 för 0-9, och sedan införs bokstäverna a-f för att representera 10-15.
Hexadecimaler har en mycket bekväm omvandlingsrelation till bitar och det kan därför med stor fördel användas i programmeringen. Varje hexadecimal representeras nämligen alltid med 4 bitar som kan placeras efter varandra, så här:

0000 = 0
0001 = 1
0010 = 2
0011 = 3
0100 = 4
0101 = 5
0110 = 6
0111 = 7
1000 = 8
1001 = 9
1010 = a
1011 = b
1100 = c
1101 = d
1110 = e
1111 = f

Efter f kommer det hexadecimala talet 10, som ju har beloppet 16. I det binära talsystemet får vi 10000. Fyller vi nu på med tre nollor vänsterifrån får vi 00010000, och då ser vi med all tydlighet att 0001 0000 = 10.

Det binära talsystemet kan också användas för att representera negativa tal och fraktioner av heltal. På samma sätt som i det decimala talsystemet sätter man en punkt (eller kommatecken) till höger om det minsta heltalet för att notera att potenserna får negativ exponent. Exempelvis kan 11.01 användas för att representera 2.25, som är fallet i fixpunktsrepresentation. I datorer används oftast en flyttalsrepresentation som vi kommer att gå igenom lite senare.
Negativa tal skriver man enkelt genom så kallad tvåkomplementsrepresentation, som följer naturligt av att ett binärt tal med ett fixt antal bitar aldrig kan få fler eller färre bitar vid behov. Om man exempelvis adderar 1 till det 8 bitar långa talet 1111 1111, och det inte går att trolla fram fler bitar, måste summan bli 0000 0000, vilket ju är 0. Omvänt blir 0 – 1 = 0000 0000 – 1 = 1111 1111, alltså kan -1 representeras av 1111 1111. Man får exakt samma sak om man inverterar 0000 0001, alltså 1111 1110, och adderar 1 till detta. Då blir det 1111 1111. Följer man samma procedur för 0000 0010, alltså 2, får man 1111 1101 + 1 = 1111 1110, vilket får representera -2. Man inser då att det minsta talet representeras av 1000 000, som är -128, eftersom -127 är 1000 0001 (1000 0000 + 1). Av detta följer naturligtvis att 127 är det största talet som går att representera när det finns tecken med.
Tvåkomplemensrepresentation kan hanteras av alla moderna datorer.

Peace!

C-programmering – Kompilering

För att källkod ska kunna exekveras på en dator måste den kompileras. Detta görs med hjälp av ett verktyg kallat för en kompilator och det är den centrala delen i alla utvecklingsmiljöer. Det kompilatorn gör är att översätta källkod till maskinkod, och det gör den i flera steg som vi ska gå igenom här.

Den källkod som vi skrivit i C återfinns i ett textdokument av typen .c någonstans bland dokumenten. Det är vår input till kompilatorn. Det första steget i kompileringen är att utföra lexning, alltså lexikalanalys på källkoden. Lexern börjar med att scanna källkoden för att upptäcka särskilda tecken och kombinationer av tecken, för detta använder lexern en tillståndsmaskin som är kodad för varje teckenkombination. Därefter utvärderas varje kombination och lexern producerar motstvarande värden som den skickar vidare.
Direktiv som börjar med #, exempelvis #include, #pragma och #define, hanteras av preprocessorn som på den här nivån exempelvis används för att inkludera makron och särskilda kompilatordirektiv. C-filer som har preprocessats är av typen .i och sägs vara 100% källkod, eftersom alla kommentarer har rensats bort. Den här koden är egentligen källkoden i sin fulla form med makron och definitioner inbakade.

I nästa steg görs en syntaxanalys av källkoden, det vill säga för att kolla så att grammatiken är korrekt. Här används en parser för att bygga upp ett syntaxträd av programmet, det vill säga att parsern bryter ner varje sats i programmet för att kolla så att det stämmer överens med språkets grammatik. Ett exempel är att den kollar så att printf(”textsträng”); är skriven på rätt sätt. Det är inte ovanligt att man glömmer citattecknen och då genereras ett syntaxfel.
Källkoden ska sedan genomgå en semantikanalys. Här är det viktigt att datatyper stämmer överens med varandra och att variabler är deklarerade och initierade. Detta kan inte parsern göra eftersom det inte går att beskriva med EBNF-notation (Extended Backus Naur Form). Den semantiska analysen resulterar i ett slags abstrakt maskinkod. En mellankod som så här långt är oberoende av arkitektur och processor.

Det är nu som genereringen av den processorarkitekturspecifika koden börjar. Instruktioner ska bestämmas, registerallokering ska genomföras och om processorn har en pipeline ska instruktioner också schemaläggas. Vidare ska optimering göras för att effektivisera koden. Dessutom ska debugbar kod också genereras.
Det vi har efter optimeringen är objektfiler, .o-filer, med kod bestående av instruktioner ur processorns egna instruktionsset. Detta är vad kompilatorer och assemblatorer i regel producerar. en del kan gå längre och producera exekverbar kod, men .o-filer är inte exekverbara.

För att producera en exekverbar fil måste .o-filerna länkas samman med varandra och med filer från systembiblioteket. Detta görs med hjälp av en länkare som i regel har två uppgifter. Det ena är att tilldela .o-filernas innehåll adresser så att koden och variablerna hamnar rätt i minnet och det andra är att knyta ihop globala symboler. Länkaren producerar alltså den fil som innehåller exekverbar kod.

Kompileringen görs oftast genom ett klick på en ikon i de flesta utvecklingsmiljöer, men i Unixmiljöer måste det göras med ett terminalkommando. Emacs, Cygwin, Ubuntu och Debian är exempel på Unixmiljöer där gcc anropas i terminalen med hjälp av:

$ gcc -o foo foo.c

Det här kommandot kompilerar och länkar foo.c, och skapar den exekverbara filen foo. Kompilering utan -o gör samma sak, men den exekverbara filen döps till a.out automatiskt.

Kompilering kan också göras med -c, då skapas foo.o, det vill säga att länkningen uteblir.
Anropas gcc med -S skapas foo.s, det vill säga att vi skapar en assemblerfil.
Anropas gcc med -E skapas foo.i, det vill säga att vi får ut källkoden i full form.

Peace!

C-programmering – Vad är C?

Först och främst ska vi ta oss en titt på vad C är för någonting. C är ett programmeringsspråk som uppstod 1972 hos AT&T Bell Labs, där det utvecklades av Dennis Ritchie. Ursprungligen var det ett språk man utvecklade för att skriva om operativsystemet Unix och göra det mer portabelt, sedan har det kommit att bli ett av de populäraste programspråken i världen med flertalet implementationer, dialekter, standardspecifikationer och vidareutvecklingar till nya språk på högre nivåer. Java, C++, Objective C och C# är alla språk som baserats på C och därför oftast liknar C i många av sina satser. C i sin tur bygger på ALGOL, ett språk tidigare utvecklat mestadels för forskning och studier.

C är ett imperativt och strukturbaserat språk. Det innebär dels att programflödet påverkas av satser och dels att programmet är indelat i block, subrutiner (funktioner) och loopar. All exekverbar kod förekommer i funktioner, även huvudfunktionen main är en funktion. Anrop till funktioner som subrutiner kan göras och man kan skicka data till dessa genom att ange funktionsparametrar. Det går lika bra att skicka data till den anropande funktionen genom att ange vad som ska returneras. Referenser är också möjliga att skicka, detta gör man med hjälp av pekare. Data hanteras med hjälp av variabler och pekare som antingen är heltal eller flyttal, dessa har olika datalängder hos olika arkitekturer.
Satser och block avgränsas med semikolon och klammerparenteser (även kända som måsvingar, { }). Måsvingarna används alltid för att börja och avsluta en funktion.

C förekommer i ett antal standarder, dessa är olika versioner av C som i språkets yngre dagar utvecklades för olika plattformar. Än idag förekommer olika standarder som är plattformsberoende, men det finns en standard som används rent generellt. Denna kallas för ANSI C. American National Standards Institute (ANSI) samlade ihop en kommitté för att standardisera språket, vilket gav upphov till det som kallas ANSI C, eller C89, eller Standard C. International Organization of Standardization (ISO) adopterade denna standard och den finns så att säga hos de vanligaste implementeringarna idag. ANSIs standard har dessutom vidareutvecklats sedermera och innehåller flertalet tillägg. Program som är utvecklade inom ramarna för dessa standarder möter inga kompatibilitetsproblem mellan implementeringarna.

Det finns många implementeringar av C, den som finns tillgänglig för allra flest plattformar är GNU Compiler Collection (GCC). GCC består av en samling kompilatorer för ett antal språk, exempelvis C, C++, Objective C och Java. GCC ingår i GNU-projektet och är 100% gratis programvara, det vill säga freeware. Den förekommer i de flesta gratis nerladdningsbara utvecklingsmiljöer för såväl Windows som Unix. Förutom GCC finns även Intel C, Clang, MSVC, Pelles C och många många andra.
C-Dialekter är språk som är alldeles för lika C för att verkligen kunna klassas som egna språk. Oftast handlar det om tillägg i språket som gör det möjligt att arbeta med speciella arkitekturer eller miljöer. Ett exempel är Unified Parallel C som utvecklats för effektiv programmering av superdatorer. Ett annat är Cyclone som anses vara en säker dialekt som undviker de sårbarheter som förekommer i C.

C är mycket användbart på flertalet fronter, exempelvis systemprogrammering, inbyggda system och utveckling av nya språk och operativsystem. C besitter flertalet goda egenskaper så som tillgång till adresser i minnet, type punning (exempelvis typkonvertering), stöd för att programmera websidor, och möjligheter att agera som mellanspråk för implementering av nya språk. C kan likaväl användas för programmering av algoritmer på hög nivå. C är med andra ord ett mycket universellt programspråk.

Peace!

Vintern är tillbaka

Nej se det snöar, nej se det snöar, det var väl roligt, hurra!

Nej för helvete, vintern är över! Våren borde komma nu…

Men det är ju det som är grejen med påskväder. Det är så jävla volatilt.

Själv har jag kommit till en punkt här i livet där jag ifrågasätter poängen med den här bloggen. Jag har haft den i snart 9 år. Jag har bloggar här på finest i snart 9 år. Det är helt jävla otroligt. Men det börjar kanske bli läge att gå vidare. Vad vet jag egentligen?

Jag tänker i alla fall publicera en programmeringsskola från och med nu. Jag tänker inte längre hålla på och blogga om en massa blaj som ofta inte betyder någonting. Jag vill publicera något mer betydelsefullt. Därför blir det nu en programmeringsskola. Varför inte egentligen?

Peace!

Ploërmel till Stockholm

Något av det coolaste som finns är timelapsevideor där folk ställer kameran vid vindrutan och kör tusentals kilometer. I denna video är det någon som kör från Ploërmel i Bretagne till Stockholm, det är en resa på 2500 km som tar 1½ dagar. Detta snabbar man upp för att det ska ta 1½ timmar.

Sitt och njut av grym techno och sommarvärme på europeiska motorvägar.

Det coola är ju hur de har lyckats klippa in vägskyltar och andra bilder för att hela tiden hålla tittaren uppdaterad om vart de är. XD

Peace!

Eid Mobarak

Nu är det alltså vårdagjämning och nu börjar dagarna bli längre än nätterna, vilket betyder att sommarn är på väg.

Och jag får lov att önska alla som firar eller har firat en riktigt härlig norooz.

Peace!