Tässä oppaassa tutustutaan C-kielen alkeisiin ja mm. seuraaviin asioihin:
Ohjelmointi tarkoittaa tietokoneohjelman kirjoittamista. Tietokone suorittaa vain konekielisiä ohjelmia. Konekieli on ihmiselle vaikeaselkoista, joten ihmiset eivät kirjoita ohjelmia konekielellä vaan jollakin ohjelmointikielellä. Tätä ihmisen kirjoittamaa "tekstiä" kutsutaan lähdekoodiksi (tai koodiksi). Tämän vuoksi ohjelman kirjoittamista kutsutaan myös koodaamiseksi.
Kuten sanottu tietokone ei siis osaa suorittaa ihmisen kirjoittamaa lähdekoodia, vaan se on ensin muutettava konekieliseksi. Muuttaminen voi tapahtua joko kääntäjän tai tulkin avulla. Kääntäminen tarkoittaa sitä, että kääntäjäksi kutsuttu tietokone-ohjelma lukee lähdekoodin sisältävän tiedoston ja muodostaa sen perusteella uuden tiedoston, joka on konekielinen. Tämä konekielinen tiedosto voidaan nyt suorittaa tietokoneella, eikä sen suorittamiseksi tarvita enää lähdekoodia, eikä kääntäjää. Kääntäjän muodostama konekielinen tiedosto voidaan siirtää toiseen koneeseen ja suorittaa siinä vaikkei tässä koneessa ole mainittua kääntäjää eikä lähdekoodia.
Tulkitseminen tarkoittaa sitä, että uutta erillistä tiedostoa (konekielistä) ei muodosteta, vaan aina kun ohjelma suoritetaan tulkki lukee lähdekoodin ja antaa konekielisen koodin tietokoneen suoritettavaksi ikään kuin lennosta. Tällä tavalla luodun sovelluksen voi suorittaa vain koneessa, jossa on mainittu tulkki.
Osa ohjelmointikielistä on käännettäviä ja osa tulkittavia. Käännettäviä ohjelmointikieliä ovat esimerkiksi C ja C++. Java ja C# käännetään yleensä välikieleksi, joka suoritetaan ajonaikaisessa ympäristössä. Tulkittavia kieliä ovat esimerkiksi Python ja PHP. JavaScript suoritetaan yleensä selaimen tai muun ajonaikaisen ympäristön avulla.
Tietokoneohjelman eli sovelluksen kannalta tietokoneen keskeisimmät osat ovat prosessori, kiintolevy ja keskusmuisti (RAM). Käyttäjän kirjoittama lähdekoodi ja kääntäjän tuottama konekielinen tiedosto tallennetaan kiintolevylle, jossa tiedostot säilyvät myös koneen sammuttamisen jälkeen. Kun ohjelma käynnistetään, se tai osa siitä ladataan keskusmuistiin. Keskusmuisti on jaettu tavun (8 bittiä) kokoisiin lohkoihin eli muistipaikkoihin. Suoritin suorittaa ohjelmaa lukemalla peräkkäisiä muistipaikkoja alueelta, johon ohjelmakoodi on tallennettu, ja tulkitsemalla lukemansa bittijonot konekielisiksi käskyiksi. Kullakin muistipaikalla on osoite, jota tarvitaan kertomaan suorittimelle, mistä muistipaikasta sen tulee kulloinkin lukea tietoa.
Oheinen kuva havainnollistaa keskusmuistin rakennetta ja muistipaikkojen osoitteita. Kuhunkin muistipaikkaan voi siis tallentaa yhden tavun eli 8 bittiä.
Seuraavassa esimerkissä luodaan merkkityyppinen muuttuja nimeltään myVariable ja sen arvoksi sijoitetaan a-kirjain. Sitten tulostetaan:
Alla on esimerkin lähdekoodi:
#include <stdio.h>
int main()
{
char myVariable='a';
printf("Muuttujan myVariable arvo = %c\n",myVariable);
printf("Muuttujan myVariable osoite = %p\n",&myVariable);
printf("Muuttujan myVariable arvo desimaalimuodossa = %d\n",myVariable);
return 0;
}
Ja ohjelma tulostaa seuraavat rivit:
Muuttujan myVariable arvo = a Muuttujan myVariable osoite = 00000065eb7ff92f Muuttujan myVariable arvo desimaalimuodossa = 97
Nyt tietokoneen muistissa on seuraavanlainen data
Binääriluku 0110 0001 on desimaalimuodossa luku 97. ASCII-merkkikoodauksessa se vastaa merkkiä a.
ASCII-merkit löytyvät esimerkiksi sivulta https://www.asciitable.com/
Huomaa:
Tämän kurssin kannalta heksadesimaalilukujen ymmärtäminen ei ole tärkeää, mutta voit lukea niistä sivulta https://fi.wikipedia.org/wiki/Heksadesimaalij%C3%A4rjestelm%C3%A4
Pinomuisti on alue RAM-muistissa, jota käytetään funktioiden kutsumiseen ja paikallisten muuttujien säilyttämiseen. Kun funktio kutsutaan, sen tiedot "pinotaan" muistiin, ja kun funktio päättyy, nämä tiedot poistetaan automaattisesti.
Pino toimii LIFO-periaatteella (Last In, First Out), eli viimeksi lisätty tieto poistetaan ensimmäisenä.
Pinomuistin koko on rajallinen, ja liiallinen käyttö (esimerkiksi liian syvä rekursio tai suurien taulukoiden luominen pinomuistiin) voi johtaa stack overflow -virheeseen. Ohjelmoijan tulee siksi olla tietoinen pinon käytöstä ja välttää sen loppumista.
Rekursio tarkoittaa ohjelmointitekniikkaa, jossa funktio kutsuu itseään suorittaakseen tehtävän osissa. Jokainen uusi kutsu varaa tilaa pinomuistista, ja jos kutsuja kertyy liikaa (esimerkiksi jos lopetusehto puuttuu tai on virheellinen), pinomuisti voi täyttyä ja aiheuttaa stack overflow -virheen. Rekursio on hyödyllinen mm. tietorakenteiden läpikäynnissä ja matemaattisissa ongelmissa, mutta sitä tulee käyttää harkiten.
Kekomuisti on toinen RAM-muistin osa, jota käytetään dynaamiseen muistinvaraukseen ohjelman ajon aikana. Tiedot kekomuistissa säilyvät niin kauan kuin ohjelmoija itse vapauttaa ne. Tämä on hyödyllistä esimerkiksi silloin, kun ei tiedetä etukäteen kuinka paljon muistia tarvitaan.
#include <stdlib.h>
int* dyn = (int*)malloc(100 * sizeof(int)); // varataan 100 intin taulukko
free(dyn); // vapautetaan varattu muisti
Kekomuistin käyttö ei ole rajattu samalla tavalla kuin pinomuistin, ja sitä voidaan varata tarpeen mukaan. Kuitenkin, esimerkiksi C-kielessä, ohjelmoijan vastuulla on itse vapauttaa varattu muisti. Jos tämä unohtuu, seurauksena voi olla muistivuoto (memory leak), joka heikentää suorituskykyä ja voi lopulta johtaa muistin loppumiseen. Joissakin muissa ohjelmointikielissä, kuten Java ja Python, käytössä on roskienkeruu (garbage collection), joka huolehtii dynaamisen muistin vapauttamisesta automaattisesti.
Dynaamisesta muistinvarauksesta kerrotaan lisää kohdassa Dynaaminen muistinvaraus.
Swappaus (tai sivutus) on muistinhallintatekniikka, jossa käyttöjärjestelmä siirtää osan keskusmuistin sisällöstä väliaikaisesti levymuistille, kun fyysinen RAM-muisti ei riitä. Tämän avulla voidaan suorittaa suurempia ohjelmia tai useampia prosesseja samanaikaisesti, mutta suorituskyky voi heikentyä, koska levymuisti on RAM-muistia huomattavasti hitaampaa.
Swap-tilan käyttö näkyy käyttöjärjestelmän resurssienhallinnassa, ja sen määrää voidaan yleensä säätää erikseen järjestelmän asetuksista.
C-ohjelmointikieli kehitettiin 1970-luvulla, ja se on edelleen yksi laajimmin käytetyistä ohjelmointikielistä. Myöhemmin C-kielestä kehitettiin laajennettu ohjelmointikieli C++, johon lisättiin muun muassa olio-ohjelmoinnin ominaisuuksia.
C-kielisiä ohjelmia voidaan kehittää eri käyttöjärjestelmissä, kuten Windowsissa, Linuxissa ja macOS:ssä. Näissä ympäristöissä käytetään yleensä kyseiselle käyttöjärjestelmälle sopivia C-kääntäjiä, jotka muuntavat ohjelmakoodin tietokoneen suoritettavaksi konekieleksi.
Lähdekoodi on ihmisen kirjoittamaa ohjelmakoodia, joka on kirjoitettu jollain ohjelmointikielellä, kuten C, Python tai Java. Se sisältää ohjelman ohjeet ja logiikan ja se täytyy kääntää tai tulkata, jotta tietokone voi suorittaa sen.
C-kieliselle lähdekooditiedostolle käytetään yleensä tarkenninta .c (.cpp on yleensä C++-koodia sisältävä tiedosto). Header-tiedostojen tarkennin on .h ja niissä on kuvattu ohjelmassa käytettävien funktioiden rakenne. Funktioiden varsinainen koodi on kuitenkin .c-tiedostoissa.
Windowsissa suoritettavien sovellusten tarkennin on .exe. Jos siis kirjoitetaan suoritettava sovellus nimeltään myApp, on C-kielisen kääntäjän tuottama suoritettava tiedosto nimeltään myApp.exe. Linuxissa ja macOS:ssä suoritettava tiedosto on yleensä ilman päätettä eli se olisi myApp.
Yksinkertaisesti voidaan kuvata C-sovelluksen luontia näin:
Header-tiedostot (pääte .h) ovat olennainen osa C-ohjelmointia. Ne sisältävät tyypillisesti funktioiden esijulistuksia, makromäärittelyjä, tietorakenteiden määrittelyjä sekä muita vakioita, joita voidaan käyttää useassa lähdekooditiedostossa.
Header-tiedosto sisällytetään lähdekoodiin #include-komennolla:
#include "omaheader.h"
#include <stdio.h>
Kulmasulkeita (<>) käytetään järjestelmän vakioheaderien kanssa, kun taas lainausmerkkejä ("") käytetään omien tiedostojen kohdalla.
Jotta header-tiedoston sisältö ei tulisi sisällytetyksi useita kertoja, käytetään ns. include-vahtia:
#ifndef OMAHEADER_H
#define OMAHEADER_H
// Sisältöä
#endif
Tällainen rakenne estää kaksoismäärittelyt ja mahdolliset käännösvirheet.
Header-tiedostossa ei tule toteuttaa funktioita (paitsi mahdollisesti inline-funktioita).
Funktioiden toteutukset kuuluvat .c-tiedostoihin.
Kirjasto on valmiiksi tehtyä ohjelmakoodia, jota oma ohjelma voi käyttää. Ohjelmoija voi käyttää valmiita, toisten tekemiä kirjastoja, mutta hän voi myös tehdä kirjastoja itse. Kirjastossa voi olla esimerkiksi valmiita funktioita, joita ei tarvitse kirjoittaa aina uudelleen. C-ohjelmissa käytetään kirjastoja esimerkiksi tulostamiseen, tiedostojen käsittelyyn ja matemaattisiin laskuihin.
Kirjasto ei yleensä ole itsenäinen ohjelma, vaan se liitetään osaksi omaa ohjelmaa käännöksen tai ohjelman käynnistyksen yhteydessä. Tässä vaiheessa riittää ymmärtää, että kirjasto tuo ohjelmaan valmista toiminnallisuutta.
| Tiedostopääte | Tiedostotyyppi |
|---|---|
| .a | static library file |
| .c | C language file |
| .cpp | C++ language file |
| .dll | Dynamic Linked Library file (Windows) |
| .h | C Header file |
| .lib | Static library or import library file (Windows) |
| .o | Object file (Unix/Linux) |
| .obj | Object file (Windows) |
| .so | Dynamic shared library file (Linux/Unix) |