ITKA203 Käyttöjärjestelmät -kurssin Demo 3 keväällä 2015, 2016 ja 2017 ja 2018 ja 2019 ja 2020. "Lähespikaintro C-kielellä ohjelmointiin"
Paavo Nieminen, paavo.j.nieminen@jyu.fi
Jyväskylän yliopiston tietotekniikan laitos.
STATUS: Varmuudella käyttökelpoinen 2019. Tehdään pieniä tarkennuksia, jos havaitaan tarpeelliseksi.
Contents
Tämä harjoite perustuu demoissa 1 ja 2 esiteltyihin perustaitoihin, jotka oletetaan alustavasti tunnetuksi ja joita koko ajan harjoitellaan lisää. Erityisesti tulisi jo olla jollain tapaa miellyttävää toimia pääteyhteyden yli ja editoida lähdekoodia (=tekstitiedosto) yhdessä screen-ikkunassa ja käyttää komentoriviä toisessa. Komentojen ja argumenttien antaminen shellissä pitäisi olla tuttua samoin kuin ohjelmien tulosteiden tulkitseminen päätteeltä ja interaktiivisten tekstipohjaisten ohjelmien käyttely ym.
Huomioi seuraavaa:
- Käännökset ja kokeilut on tarkoitus tehdä pääteyhteydellä yliopiston etäkoneessa jalava.cc.jyu.fi tai halava.cc.jyu.fi tai itka203-testi.it.jyu.fi.
- Jomman kumman suorakäyttökoneille asennetun tehokkaan tekstieditorin (emacs tai vim) opettelu on suositeltavaa. Selviytymisohjeet suomeksi löytyvät demosta 2, mutta toki ohjelmien oman englanninkielisen tutoriaalin läpikäynti on aina paras vaihtoehto. Netistä löytyy myös paljon hyviä videotutoriaaleja molempiin editoreihin!
- Tekstieditoinnin voi tehdä absoluuttisessa hätätilanteessa helppokäyttöisellä nano -editorilla... Syntaksin mukainen väritys auttaa elämässä sen verran, että myös karvahattu-Nano kannattaa laittaa värittämään sen minkä se pystyy. Kopioi siis demossa linkitetty nanorc_patka -niminen tiedosto kotihakemistoosi tiedoston ~/.nanorc jatkeeksi, jos se ei vielä tue C-kielen väritystä. Pyydä tähän apuja lähiohjauksessa, jos tuntuu vaikealta.
- Ei tarvitse pelätä että tällä kurssilla tehtäisiin kovin suuria ohjelmointisuorituksia. Enemmänkin katsellaan ja yritetään ymmärtää. Demotilaisuuksissa tai sähköpostitse tarjotaan apua, jos ei muuten aukene. Mikäli ohjelmointi tuntuu hankalalta ymmärtää, on syytä kerrata kurssin Ohjelmointi 1 asioita.
Harjoituksen tavoitteet:
- Teet omin käsin käytännön toimenpiteitä, joita luentomonisteessa ja mahdollisissa luentoesimerkeissä näytetään, jolloin niiden merkitys toivon mukaan konkretisoituu. Erityisesti käännät ja suoritat C-kielisen ohjelman.
- Näet alustavasti, millaisia asioita POSIX-standardi määrittelee löytyväksi yhteensopivan järjestelmän "matalan tason" kirjastoissa.
- Perinteisistä ohjelmien toiminnan mukauttamiseen vaikuttavista keinoista käydään tarkoin läpi komentoriviargumentit ja ympäristömuuttujat.
Keväällä 2015 määriteltyjen osaamistavoitteiden osalta demon tehtyään opiskelija:
Alustavalla tasolla C-koodin (ei vielä bashin) osalta opiskelija:
Lisäksi tähdätään edelleen useaan muuhun osaamistavoitteeseen, jotka viedään perille myöhemmissä demoissa.
Aiemmissa demoissa teit oman kotihakemistosi alle hakemistot Käyttöjärjestelmät-kurssin ensimmäisille harjoitteille. Ota jälleen pääteyhteys IT-palveluiden suorakäyttökoneeseen, muistele miten screeniä käytellään näppärästi, ja aseta sopivaksi katsomassasi screen-ikkunassa työhakemistoksesi tämän kolmannen demon hakemisto, nimeltään esimerkiksi ~/kj20/demo3/ tai muuta vastaavaa. (Muistele tarvittaessa aiempia demoja: miten hakemisto vaihdettiin, miten varmistettiin, mikä on nykyinen työhakemisto, jne. ...)
Kun olet demo 3:n hakemistossa, hae esimerkkikoodi kurssin materiaalitietovarastosta, eli komenna shellissä:
wget https://yousource.it.jyu.fi/itka203-kurssimateriaalikehitys/itka203-kurssimateriaali-avoin/blobs/raw/master/2015/esimerkit/l04_helloworld.c
Huomautus 1: Pitkä ja hankala URL varmaan kannattaa varovaisesti kopioida ja liimata pääteyhteysohjelmaan.. Varmista, että olet varmasti kopioinut komennon tai URLin web-selaimesta leikepöydälle. Kun olet varma, että leikepöydällä on täsmälleen ylläoleva komento, uskallat liittää sen pääteyhteysikkunaan shelliin, joka odottaa komentoa. KiTTYssä ja PUTTYssä klikataan hiiren oikeanpuoleista nappia; Linux-käyttäjät klikkaavat keskimmäistä nappia ja Mac-käyttäjät tekevät mahdollisesti jotain muuta. Tavoitteena on tavalla tai toisella suorittaa yllämainittu komento, joka hakee Internet-yhteyden yli WWW:stä yhden tiedoston.
Huomautus 2: Free software foundationin ohjelma wget ei ole määritelty POSIX-standardissa, mutta se on niin näppärä, että käytämme sitä nyt. POSIX kyllä määrittelee samaan tarkoitukseen ohjelman curl, jota voi käyttää yhteensopivuuden takaamiseksi skripteissä.
Hae vielä seuraava:
wget https://yousource.it.jyu.fi/itka203-kurssimateriaalikehitys/itka203-kurssimateriaali-avoin/blobs/raw/master/2015/demot/mallikoodia/d03_fiilikset.c
Ja mikäli suosituksista huolimatta käytät tekstin editointiin nanoa, etkä vimiä tai emacsia, niin tee ihmeessä myös seuraavat operaatiot, jotta C-koodi näyttää mielekkäämmältä editorissa:
wget https://yousource.it.jyu.fi/itka203-kurssimateriaalikehitys/itka203-kurssimateriaali-avoin/blobs/raw/master/2015/demot/mallikoodia/nanorc_patka cat nanorc_patka >> ~/.nanorc
Huomautus 3: Etäpalautuksen vuoksi emme täydellisesti pysty toteamaan, että olet tehnyt noin viisitoista minuuttia kestävän palautustehtävän lisäksi kaikki tehtävät aivan itse ja oppinut niistä -- jokainen on oman onnensa nojassa, ja "teknisesti sallittu" laiskuus on jokaisen oma häpeä. Tentti sen sitten kertoo, mitä itse kukin on oppinut. Näillä sanoin työn touhuun... Kaikki tämän demon vaiheet kuuluvat kurssin sisältöön!
Katso tekstieditorilla ohjelmaa l04_helloworld.c -- siitä nähdään yksinkertaisen C-ohjelman muoto. Tällainenhan se on (rivinumerot lisätty tulosteeseen):
1 #include <stdio.h> 2 int main(int argc, char **argv){ 3 printf("Hello World!\n"); 4 return 0; 5 }
Selitetään merkitys rivi riviltä, ja peilataan olio-ohjelmoinnista (esim. C# tai Java) tuttuihin asioihin. Syntaksiin mennään tarkemmin myöhemmin; nyt mietitään vain rivien merkitystä ohjelman kannalta.
Rivi 1:
#include <stdio.h>
Tällä liitetään mukaan tiedosto stdio.h. Tällaiset .h -päätteiset tiedostot ovat niin sanottuja otsikkotiedostoja (Header file), joiden perusteella C-kielen kääntäjä tietää, millaisen rajapinnan jokin kirjasto (tai kirjaston osa) tarjoaa. Nimenomainen stdio (Standard input/output) tarkoittaa perusmallin syöttö- ja tulostusvälineistöä, jollainen aina löytyy ja jota vastaava kirjastokin liitetään ohjelmaan ilman erillistä pyyntöä. Tässä ohjelmassa tulostetaan printf() -aliohjelmalla, jonka otsikko on nimenomaan tiedostossa stdio.h. Lähin vastine esimerkiksi Javassa on import -avainsana, jolla lähdekooditiedoston alussa ilmoitetaan, minkä nimenomaisen kirjaston rajapintaa halutaan käyttää - esimerkiksi fi.jyu.mit.kurssit.munkurssi.Harpake voi olla aivan erilainen kuin joku.muu.organisaatio.kirjasto.Harpake. Tiedoston alussa voisin haluta kirjoittaa siis import fi.jyu.mit.kurssit.munkurssi, minkä jälkeen Harpake on se luokka, jota oikeasti tarkoitan. Samoin kuin oliokielisten lähdekoodien alussa on paljon import -rivejä, C-ohjelmien alussa on usein paljon #include -rivejä.
Otsikkotiedosto sisältää vain tiedon rajapinnan määrittelystä (eli kunkin aliohjelman nimen sekä sen parametrien ja paluuarvon tyypit); itse kirjastot on liitettävä vielä erikseen joko lähdekoodina (käännöksen yhteydessä) tai käännettyinä eli ns. binäärisinä kirjastoina (linkitysjärjestelmän avulla, mikä on tyypillinen ja joustavampi tapa). Muista, että kaikkia asioita voidaan katsoa tarkemmin luennoilla, sikäli kuin kysymyksiä herää ja esitetään.
Konkretiaa halajaville todettakoon, että otsikkotiedosto stdio.h on suorakäyttökoneellamme sijainnissa /usr/include/stdio.h, mistä se löytyy automaattisesti, koska koneelle asennettu kääntäjä osaa oletusarvoisesti etsiä otsikoita mm. hakemistosta /usr/include. Otsikkotiedostossa luvatulla rajapinnalla varustetun aliohjelman printf() konekielinen koodi puolestaan on osa kirjastoa, joka löytyy suorakäyttökoneellamme sijainnissa /lib64/libc.so.6. Ohjelman käynnistysvaiheessa käyttöjärjestelmän ns. lataus- ja linkityskoodi osaa ladata ja liittää suoritettavan prosessin koodiin juuri tuon kirjaston, koska ELF-muotoiseen ohjelmatiedostoon on tallennettu tieto kaikista tarvittavista kirjastoista. Tämä libc sisältää kaikkein yleisimmät ja useimmin C-kielessä tarvitut, standardin määrittelemät kirjastoaliohjelmat, joten se liitetään ohjelmiin automaattisesti ilman erillistä pyyntöä. (Mikään pakko ei ole käyttää libc:tä, jos vaikkapa hullutellakseen tai muusta syystä pitää karsia ohjelmatiedoston koko absoluuttiseen minimiin - silloin pitää kuitenkin käännösvaiheessa suorastaan kieltää kääntäjää hyödyntämästä standardikirjastoa). Muut kirjastot täytyy erikseen ilmoittaa, kun suoritettava ohjelma luodaan kääntäjällä ja muilla apuohjelmilla. Mitään mystiikkaa näihinkään asioihin ei liity, mikä on hyvä tiedostaa heti aluksi.
Rivi 2:
int main(int argc, char **argv){
Tästä alkaa C-ohjelman suoritus. On sovittu C-kielen määrittelyssä sekä sitä kautta myös mm. POSIX-standardissa, että ohjelman suoritus tarkoittaa (kyseisessä laitteistossa ja käyttöjärjestelmässä tarvittavien, automaattisesti tapahtuvien alustusoperaatioiden jälkeen) sellaisen aliohjelman kutsumista, jossa:
- nimi on main
- paluuarvo on int eli kokonaisluku
- on kaksi parametria, joista ensimmäinen on tyypiltään int ja toinen on tyypiltään char** eli osoitin merkkijonotaulukkoon (älä vielä huoli tietotyypeistä, niihin syvennytään seuraavassa demossa).
Tämä on ihan samanlainen sopimusasia kuin se, että C#-luokan suoritus tarkoittaa seuraavanlaisen luokkametodin kutsumista:
public static void Main(string[] args)
Tai että Java-luokan suoritus tarkoittaa seuraavanlaisen luokkametodin kutsumista:
public static void main(String[] args)
Huomataan, että C, kuten C# tai Javakin, on lohkorakenteinen, ja lohkojen syntaksikin on kaikissa näissä kielissä täysin sama: lohko alkaa kiharasululla { ja päättyy vastaavaan käänteiseen sulkuun }. Muutenkin syntakseissa on paljon samaa. Esimerkiksi C-kielen aliohjelmat aloitetaan muodossa:
PALUUARVON_TYYPPI aliohjelmanNimi(TYYPITETTY_PARAMETRILISTA)
joka on täysin sama kuin C#:n tai Javan metodimäärittelyn syntaksi (varmasti ainakin sen takia, että nämä myöhemmät kielet on tarkoituksella tehty samannäköiseksi kuin C). Myös C:ssä aliohjelmamäärityksen eteen laitetaan tarvittaessa lisämääreitä. Kaikki lisämääreet eivät kuitenkaan tarkoita samaa uudemmissa kielissä! Esimerkiksi static on C-kielessä ihan eri asia kuin C#:ssa tai Javassa. Päivän analogia: "pulma" on suomeksi ongelma ja viroksi häät. Pitää mielellään aina tietää, millä kielellä puhutaan.
Rivi 3:
printf("Hello World!\n");
Tällainen on C:ssä aliohjelmakutsun syntaksi. Eikö näytäkin samalta kuin C#:ssa tai Javassa metodin käyttäminen? Paitsi että usein oliokielissä kutsutaan pistenotaatiota käyttämällä jonkun olion instanssimetodia; esimerkiksi C#-kutsussa System.Console.WriteLine("juu") ja vastaavassa Java-kutsussa System.out.println("juu") kutsutaan System -luokan luokka-attribuuttina löytyvän Console tai out-nimisen viitteen osoittamana löytyvän luokan instanssille metodia WriteLine tai println, ja metodille annetaan parametriksi viite vakiopoolissa sijaitsevaan string tai String -luokan instanssiin.
C:n tulostus on vähän lyhyempi selittää täsmällisesti kuin oliokielten luokkahässäkät: esimerkin rivi 3 siirtää prosessorin suorituksen aliohjelman printf ensimmäiseen käskyyn, sen jälkeen kun parametriksi on laitettu merkkijonon Hello world!\n ensimmäisen merkin eli kyseisen H:n osoite tietokoneen muistissa.
Huomaa, että C:n lauseet tulee päättää puolipisteellä ; ihan niinkuin C#:ssa tai Javassakin. Ylipäätään syntaktiset erot ovat erittäin pieniä. Varsinaiset erot kielissä johtuvat C:n yksinkertaisuudesta olioita tukeviin seuraajiinsa nähden.
Rivi 4:
return 0;
Ohjelmat voivat kertoa operaation onnistumisesta tai epäonnistumisesta virhekoodilla. Se on kokonaisluku, jonka ohjelman käynnistäjä saa haltuunsa. C-kielessä koodi annetaan main() -aliohjelman paluuarvona, eli tuon arvon asettaminen on viimeinen asia, minkä käyttäjän ohjelma suorittaa. Yleensä 0 tarkoittaa, että mitään virhettä ei tullut. Kokonaisluvun on tarkoitus olla etumerkitön (siis aina 0 tai positiivinen), ja esim. POSIX-standardin mukaan siitä huomioidaan vain 8 alinta bittiä. Näin ollen mahdollisia paluuarvoja on 256 kappaletta. POSIX määrää niistä osan tiettyihin tarkoituksiin. Katso tarvittaessa, mitä standardi sanoo aiheesta "exit code".
Rivi 5:
}
Kun aliohjelman sisällön määrittelevä lohko avataan ohjelman alussa, niin toki se pitää lopuksi sulkea. Ihan samoin kuin C#:ssa, Javassa ja muissakin lohkorakenteisissa kielissä, joiden syntaksi on tarkoituksella johdettu C:stä.
Käännä ja testaa ohjelmaa suorakäyttökoneessa shell-yhteydellä. Komenna:
c99 l04_helloworld.c
Jos kaikki meni hyvin, ohjelma kääntyi oletusnimelle a.out, jonka voit nyt suorittaa komennolla:
./a.out
Toivottavasti tulostui se, mitä odotitkin. Huomaa, että shell ei etsi ajettavia tiedostoja automaattisesti nykyisestä työhakemistosta. Ajaaksesi jotakin työhakemistosta, pitää kertoa eksplisiittisesti, että haluat ajaa ohjelman tästä hakemistosta eli ./ eli piste ja kauttaviiva.
Miksi oletusnimi on juuri a.out eikä jotain muuta? Kevään 2019 luennolla seikkailtiin hieman Internetissä, ja löydettiin sellainen historiallinen etymologia, että a.out tarkoittaa "assembler output" eli "kokoonpanijan tuloste". Tämän kurssin luentomonisteessa ja demolapuissa kuvaillaan nykyistä käännös- ja linkitysprosessia, jonka valossa nimi on tosiaan lähinnä historiallinen jäänne.
Välitehtävä: | Muuta ohjelma tulostamaan vaikkapa "Moikka kaikki ihqt" tai mitä nyt haluatkaan. Tallenna muutettu lähdekoodi samalle nimelle eli l04_helloworld.c, käännä uudelleen ja testaa että toimii odotusten mukaisesti. Olet nyt onnistuneesti ohjelmoinut C-kielellä! Nyt on hyvä hetki esimerkiksi juhlistaa tätä saavutusta hieman ja pitää pieni tauko, jonka aikana aiemmin opittu ehtii hiukan mennä kohti selkäydintä! |
---|
Kokeile itse, ajatuksen kanssa, luentomonisteen esimerkkejä, joilla voi tutustua tiedon esitystapoihin tietokoneessa. Samalla harjaantuu shellin käyttötaidot ja omatoiminen kokeileminen. Olennaiset esimerkit on lueteltu alla.
Näytä olennaiset tiedot työhakemiston tiedostoista, mukaanlukien lähdekooditiedosto:
ls -l
Näytä enemmän metatietoja liittyen yhteen tiedostoon:
stat l04_helloworld.c
Näytä tiedoston sisältö (mikä on eri asia kuin sen sijainti tai muut metatiedot) tavu tavulta niin kuin se on:
hexdump -C l04_helloworld.c
Näytä kääntäjän tuottaman suoritettavan ohjelmatiedoston sisältö, sivuttajaohjelmaa hyödyntäen:
hexdump -C a.out | less
Otetaan tässä demossa ensikosketus debuggeriin, jotta se tulee tutuksi mahdollisimman varhaisessa vaiheessa. Käännä ohjelma seuraavalla komentorivillä:
c99 -g -O0 l04_helloworld.c
Nyt argumentti -g ilmoittaa kääntäjälle, että käännettyyn ohjelmaan pitää laittaa mukaan virheenkorjausta eli debuggausta helpottavia asioita, kuten tieto alkuperäisen lähdekoodin tiedostonimistä ja rivinumeroista. Argumentti -O0 eli viiva, iso O-kirjain ja nolla puolestaan kieltää kääntäjää tekemästä automaattista optimointia konekielikoodiin. Tällä kurssilla käytetään mielellään kaikissa esimerkeissä optiota -O0, jotta syntyvä konekieli vastaa mahdollisimman läheisesti alkuperäistä C-ohjelmaa. Oikeiden tuotantokoodien julkaisussa puolestaan käytettäisiin esimerkiksi optiota -O3 jolloin ohjelman konekielinen koodi toimisi paljon nopeammin. Nopeutus perustuisi kuitenkin mm. konekielikäskyjen suoritusjärjestyksen muuttamiseen, konekielikäskyjen käyttämiseen eri tarkoituksiin kuin ne manuaalin mukaan on suunniteltu, muuttujien pitämiseen rekistereissä muistin sijasta ja ylimääräisen koodin generointiin ynnä muuhun automaattisesti tehtävään kikkailuun. Kääntäjän automaattisesti optimoima koodi voi olla hyvin kinkkisen näköistä suhteessa alkuperäiseen lähdekoodiin, millä emme halua vaivata päätä, kun tarkoitus on ymmärtää perusteita. Optimoimattomassa koodissa jokaista C-kielistä koodiriviä kohden generoituu korkeintaan muutamia selkeästi ymmärrettävissä olevia konekielisiä käskyjä, jotka ovat mukavan selkeästi peräkkäin.
Nyt pitäisi olla syntynyt taas suoritettava a.out, jonka koko on hieman aiempaa isompi, johtuen mukana olevasta debuggaustiedosta. Normaali suorittaminen näyttänee samalta kuin aiemmin. Nyt ajetaan ohjelmaa kuitenkin debuggerissa. Komenna shellissä:
gdb a.out
Mitäs nyt? Tilanne näyttää seuraavalta ja vaatii hieman selitystä:
[nieminen@halava esimerkit]$ gdb a.out GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6) Copyright (C) 2010 Free Software Foundation, Inc. ... (gdb) [ja vilkkuva kursori ...]
Käynnistit debuggerin. Ohjelmointi 1:ltä pitäisi olla jossain määrin tuttu asia jokin (esim. Visual Studion tai Eclipse IDE:n) graafinen debuggeri, jolla voi opiskella ohjelman toimintaa ja jota voi käyttää virheenetsinnässä (siitä niiden nimi "de-" "bugger"). Nyt käytettävä gdb on samanlainen, mutta komentorivikäyttöinen. Tällekin on olemassa graafisia julkisivuja eli front-endejä, mutta interaktiivisten komentoriviohjelmien käyttö on yksi tämän kurssin teemoja, joten noudatamme sitä loppuun asti.
Debuggeri odottaa nyt siis tekstimuotoisia komentoja, joiden avulla voit tutkia argumenttina annetun ohjelman toimintaa. Komenna debuggeria:
run
Debuggeri käynnisti käännetyn ohjelman, antoi sen mennä niin pitkälle kuin se ilman virheitä etenee, tässä tapauksessa onnistuneeseen loppuun saakka. Tulostui toivon mukaan seuraavankaltaista:
Starting program: /autohome/nashome3/nieminen/charragit/itka203-kurssimateriaali-avoin/2015/esimerkit/a.out Hello world! Program exited normally. Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6_6.5.x86_64
Viimeinen rivi tarkoittaa, että suorakäyttökoneillamme ei ole asennettuna debug-tietoja standardikirjastoille. Ei välitetä siitä. Itse käännettyjen ohjelmien debuggaukseen tämä ei vaikuta, koska debug-tiedot laitetaan mukaan käännösvaiheessa. Ohjelman ongelmattomasta loppumisesta saatiin tässä ilmoitus. Jos ohjelma olisi kaatunut, debuggerin avulla voisi alkaa selvittelemään, miksi niin kävi...
Tekstipohjainen ohjelma on erilainen kuin graafinen ohjelma, mutta se voi olla käyttäjäystävällinen omalla tavallaan. Esim. gdb:n käyttäjäystävällisyys toteutuu mm. seuraavin tavoin:
- Komennoista saa ohjeita komentamalla help. Eli komentoja ei tarvitse muistaa, vaan ne saa esille aina ohjelman käytön yhteydessä ilman tarvetta tarkistaa ulkopuolisesta manuaalista tai netistä.
- Komennoissa on automaattitäydennys tabulaattorinäppäimellä, ja niille on lyhyitä "alias"-nimiä, jotka on nopea kirjoittaa (ks. helpit).
- Komentohistoria on käytettävissä samoilla tehokäyttönäppäimillä kuin bash-shellissä.
- Edellisen komennon voi toistaa painamalla pelkkää enteriä. Debuggerissa on tyypillistä mm. askeltaa ohjelmaa yksi rivi tai käsky kerrallaan useiden silmukkakierrosten ym. pitkien "jälkien" läpi, joten tämä on tehty yhtä helpoksi kuin enterin painelu peräkkäin.
Debuggerin käyttöliittymä on paljolti samanlainen kuin tehokkaan interaktiivisen shellin -- tai minkä tahansa hyvin tehdyn tekstipohjaisen ohjelman. Mm. suositun laskentaohjelmisto Matlabin "Command Window" on samalla tavoin kätevä. Tässä vaiheessa lienee käynyt selväksi, että tekstikomennoilla toimiva käyttöliittymä tukee luonnostaan myös komentojen täsmällistä tallentamista, toistamista ja skriptiksi asettelemista.
Käväistään vähän aikaa pois debuggerista; siitä pääsee pois komennolla:
quit
Olet nyt käyttänyt komentorividebuggeria kerran ja lopettanut sen nätisti. Seuraavaksi tutustutaan debuggeriin askelta tarkemmin. Eli askelletaan ohjelmaa läpi rivi riviltä.
Eli uudelleen vaan käyntiin:
gdb a.out
Debuggeri on jälleen ladannut pyydetyn ohjelmatiedoston, ja se on valmis avaamaan konepellin oikein kunnolla. Katsotaan ensin yleiskuvaa ohjelmasta debuggerin disassembly-ominaisuudella. Komenna seuraavasti:
disassemble /mr main
Debuggerin ohjauskomento on tässä disassemble eli "pura konekieli symboliseen, ihmisen ymmärtämään, muotoon". Oletusarvoisesti komento näyttäisi vain käskyjen muistiosoitteet ja symbolit, mutta täydellisyyden vuoksi käytetään nyt lisukkeita /m ja /r joilla mukaan liitetään debug-tiedoista saatu alkuperäinen lähdekoodi sekä todellinen muistissa sijaitseva konekielinen tavujono.
Koska ohjelmaa ei vielä ole käynnistetty, pitää gdb:lle kertoa eksplisiittisesti, mistä aliohjelmasta disassembly halutaan tehdä (toki näin voi aina tehdä muutenkin; oletuksena disassembly näytetään siitä aliohjelmasta, jonka sisällä suorituskohta milloinkin on).
Pitäisi näkyä pääohjelman assembler-koodi alkaen jotenkin seuraavasti:
Dump of assembler code for function main: 2 int main(int argc, char **argv){ 0x00000000004004c4 <+0>: 55 push %rbp 0x00000000004004c5 <+1>: 48 89 e5 mov %rsp,%rbp 0x00000000004004c8 <+4>: 48 83 ec 10 sub $0x10,%rsp 0x00000000004004cc <+8>: 89 7d fc mov %edi,-0x4(%rbp) 0x00000000004004cf <+11>: 48 89 75 f0 mov %rsi,-0x10(%rbp) 3 printf("Hello world!\n"); 0x00000000004004d3 <+15>: bf e8 05 40 00 mov $0x4005e8,%edi 0x00000000004004d8 <+20>: e8 db fe ff ff callq 0x4003b8 <puts@plt> 4 return 0; 0x00000000004004dd <+25>: b8 00 00 00 00 mov $0x0,%eax 5 } 0x00000000004004e2 <+30>: c9 leaveq 0x00000000004004e3 <+31>: c3 retq End of assembler dump.
Nyt, kun ohjelma on käännetty debug-tietojen kanssa, osaa gdb liittää assembler-tulosteeseen C-koodirivit (rivinumero ja kyseisellä rivillä oleva koodi). Tämä on havainnollista, koska nähdään suoraan, mikä koodin osa kääntyy minkäkinlaiseksi konekieleksi.
Huomautus: tämä demo on vasta työkaluun tutustumista ja ennakoiva katsaus.. tässä nähtävien käskyjen ja rekisterien roolista voidaan jutella kaikessa rauhassa kurssin mittaan, jos tarvetta havaitaan, ja näistä jatketaan pienin askelin myöhemmissä demoissa. Tässä vaiheessa riittää oppia ja kokeilla itse, miten saat käännetyn ohjelman konekielen debuggerilla avattua konkreettisesti silmiesi eteen, ja havainnoida alustavasti, minkä tyyppisiä asioita debuggeri silloin näyttää. Ja tarkoitus on saada heräämään hämmennystä sekä kysymyksiä, joiden kautta yhteiset luennot voidaan rakentaa mielekkäiksi suurimmalle osalle opiskelijapopulaatiota!
Selitystä asioille, jotka debuggeri näyttää:
- Vasemmassa reunassa on 64-bittinen heksaluku. Tämä on muistiosoite, josta käskyn konekielinen bittijono alkaa.
- Seuraavaksi on suhteellinen osoite aliohjelman alusta lähtien. Siis esim. <main+4> tarkoittaa että kyseinen käsky alkaa muistipaikasta, joka saadaan lisäämällä main() -aliohjelman ensimmäisen käskyn osoitteeseen 4. Huomataan, että x86-64:ssä konekielikäskyjen bittijonot voivat olla eri mittaisia (riippuen siitä, onko mukana vakioarvoja, ja siitä ovatko käskyt alkuperäisiä 8086-käskyjä vai myöhempiä laajennoksia).
- Seuraavaksi on konkreettinen käsky tavujonona siten kuin se on tietokoneen muistissa tallessa, peräkkäisissä osoitteissa.
- Sitten on käsky ns. AT&T-syntaksilla eli mnemonic ja operandit. Luentomonisteen konekieltä esittelevä luku kertoo näistä esimerkkejä; totuus rajapintasopimuksista on prosessorivalmistajan julkaisemassa manuaalissa. Yksi assembly-kielinen rivi ja sen konekielinen tavujono vastaavat käytännössä toisiaan lähes yksi-yhteen.
Huomautus: Suurin osa ohjelmien tarvitsemista rakenteista hoituu muutamilla peruskäskyillä, joista kaikkein yleisimpiä kiteytetään luentomonisteessa tämän johdantokurssin esimerkeiksi. Nykyiset prosessorimanuaalit ovat kuitenkin laajuudeltaan useampituhatsivuisia, johtuen niiden monipuolisuudesta: Joitakin satoja sivuja on käskyjen toiminnan kuvailua. Valtaosa nykyprosessorin käskyistä liittyy erityistehtäviin kuten liukulukulaskentaan. Myös prosessoriytimien keskinäinen synkronointi edellyttää runsaasti sivuja nykyprosessorien manuaaleissa. Erityisesti x86-64:n manuaaleja pidentää myös se, että ne joutuvat kuvailemaan myös kaikkien aiempien x86-sarjan prosessorien toiminnan, koska yhteensopivuus aiemman binäärikoodin kanssa on säilytetty. Manuaalien alkupuolella on hyödyllistä ja selkeästi kirjoitettua johdantomaista selitystä laitteiston toiminnasta. (Vinkki iltalukemiseksi, mikäli kiinnostut aiheesta enemmän.)
Nyt kun ymmärrät näkemäsi, askella vielä omin käsin läpi "Hei maailma"-ohjelma C-koodirivi kerrallaan. Pyydettäessä näytetään luennollakin.
Komennolla start debugger antaa automaattisten alkutoimenpiteiden tapahtua ja pysäyttää suorituksen main-aliohjelman ensimmäisen koodirivin kohdalle:
start
Katso ja tulkitse näkymää itse. Heitä uudelleen (gdb:n komentohistoriasta tietysti, nuolinäppäimellä!) edellinen komento:
disassemble /mr main
Katso ja tulkitse näkymää itse. Sehän on tietysti muuten sama kuin aiemmin, mutta nyt gdb näyttää vasemmassa laidassa nuolimaisen symbolin => sen rivin alussa, jossa on seuraavaksi suoritettava konekielinen käsky. Ei vielä väliä, mitä käskyt tekevät. Totea nyt vaan, mitä tapahtuu, kun askellat yhden koodirivin eteenpäin:
step
Kaikki riviin liittyvät konekielikäskyt on nyt tehty ja debugger on pysäyttänyt suorituksen ennen seuraavaa lähdekoodiriviä. Totea tämä itse disassemblystä:
disassemble /mr main
Eli seuraavaa suorituskohtaa ilmaiseva nuoli => pitäisi nyt olla myöhemmässä kohdassa kuin viimeksi. Steppaile sitten vielä, ja totea tilanne:
step step
Viimeisen stepin jälkeen aliohjelman main() suoritus päättyy. Voit itse todeta saman, mikä luennollakin voidaan nähdä, jos porukka haluaa: päädytään alustakirjaston puolelle, josta järjestelmäämme ei nyt ole asennettu lähdekoodia tai debuggaustietoa, joten debuggeri ei voi paljoa enempää näyttää, vaan se antaa automaattisesti liitetyn alustus- ja lopetuskoodin hoitaa ohjelman lopputoimet. Sovellusohjelman ulkopuolista alustus- ja lopetuskoodia tarvitaan hoitamaan mukavuus- ja välttämättömyysseikkoja, jotka liittyvät tiettyyn käyttöjärjestelmätoteutukseen. Esimerkiksi C-kielen määrittelyssä ohjelman virhekoodiksi tulkitaan main-aliohjelman paluuarvo, mutta käyttöjärjestelmätoteutus odottaa saavansa sen käyttöjärjestelmän kutsurajapinnan kautta, johon alustariippumaton C-koodi ei edes pääse itse käsiksi. Normaalisti tarvitaan siis vielä jotakin pikkukoodia, joka tekee järjestelmäsidonnaiset alkutoimet, kutsuu mainia, ja suorittaa lopputoimet.
Välitehtävä: | Edellä kerrottuja ohjeita tai luentoesimerkkiä seuraten, lataa debug-tiedot sisältävä helloworld-ohjelma debuggeriin, ota sen aliohjelmasta main omin käsin disassembly, jossa näkyy alkuperäiset lähdekoodirivit ja syntynyt konekieli. Askella ohjelma läpi lähdekoodirivi kerrallaan step-käskyllä. |
---|
Ohjelman toimintaan voi perinteisesti vaikuttaa mahdollisen graafisen tai tekstimuotoisen käyttöliittymän ja syötetiedostojen ym. reaaliaikaisten keinojen lisäksi kahdella vakiintuneella (ja mm. POSIXin edellyttämällä) tavalla: komentoriviargumenteilla ja ympäristömuuttujilla.
Jotta yliopittaisiin samalla myös tähän asti nähtyjen shell-komentojen argumenttien periaate, katsotaan päällisin puolin pääsyä argumentteihin C-kielessä ja Javassa. Samalla nähdään, miten tietyt syntaksit voivat olla identtisiä C:ssä ja Javassa. Tässä käytetään nyt Javaa, koska sille löytyy kääntäjä suorakäyttökoneelta. Ohjelmointi 1 -kurssilta tutumpi C# on hyvin lähellä Javaa, joten tässä ei tapahdu kovinkaan isoa hyppäystä esitietokurssiin nähden.
Komentoriviargumentit Javassa:
public class Argumentit{ public static void main(String[] args){ int i; System.out.printf("Ohjelman saamat komentoriviargumentit ovat:\n"); for(i=0; i<args.length; i++){ System.out.printf("Argumentti %d: %s\n", i, args[i]); } } }
Komentoriviargumentit C:ssä:
#include <stdio.h> int main(int argc, char **argv){ int i; printf("Ohjelman saamat komentoriviargumentit ovat:\n"); for (i=0; i<argc; i++){ printf("Argumentti %d: %s\n", i, argv[i]); } }
Katselu ja ymmärtäminenkin riittää, mutta jos haluat kokeilla, niin nämä ohjelmat voi kirjoittaa (tai copy-pastata) tekstieditorilla pääteyhteydessä ja kääntää suorakäyttökoneella komentamalla:
javac Argumentit.java c99 argumentit.c
Sitten voisit konkreettisesti todeta että syntyi käännetyt tiedostot Argumentit.class ja a.out Ne voi ajaa esim. komentamalla:
java Argumentit kissa koira ./a.out kissa koira
Tulosteita voisi tarkastella ja erilaisia argumentteja voisi kokeilla kissan ja koiran tilalle. Huomioita:
- Tavukoodiksi käännetyn Java-ohjelman ajamista varten käynnistetään itse asiassa java -niminen virtuaalikoneohjelma, jonka ensimmäisen argumentin pitää olla ajettavan luokan nimi (ilman tarkennetta .class); kaikki loput argumentit välittyvät suoritettavalle Java-ohjelmalle main-metodin taulukkoparametriksi nimeltään args.
- C-käännös on suoraan ajettavissa x86-64:ssä, joten Linux lataa ja käynnistää sen suoraan, ja tiedottaa kaikki argumentit komentoriviltä ohjelmalle.
- C-käännös itse asiassa saa ensimmäisenä argumenttina (indeksi 0) ohjelman nimen siten kuin käyttäjä sen kirjoitti. Javassahan tuli vain argumentit eikä ohjelman nimeä. C-ohjelman osalta POSIX määrittelee kevyellä sanamuodolla ("should") että indeksillä nolla löytyvän argumentin "pitäisi olla nimi, joka liittyy käynnistetyn ohjelman sijaintiin". Käytännössä tänä päivänä tähän voi useimmissa järjestelmissä luottaa, eli ihan de facto standardi menettely tuo argv[0]:n ja shell-skripteissä vastaavasti $0:n sisältö vaikuttaisi olevan.
- Nykyinen Java-spesifikaatio ( https://docs.oracle.com/javase/specs/ ) ei näköjään tarkkaan ottaen määritä, mitkä merkkijonot taulukossa args tulee olla, mutta antaa esimerkkinä Unix-tyyppisen järjestelmän, jossa virtuaalikone käynnistyy juuri niin kuin keväällä 2020 näemme sen tekevän suorakäyttökoneissamme.
Alustavia huomioita syntaksista:
- for-silmukan syntaksi on tässä tapauksessa täysin sama molemmissa kielissä
- Java-kielen peruskirjastosta löytyy nykyään PrintStream-luokkassa formatoidun tulostuksen hoitava metodi printf(), joka tekee lähes samalla syntaksilla samat kätevät asiat kuin C-kielessä. Myös C# pystyy formatoituun tulostukseen vaikkapa ihan WriteLine() -metodilla. Formaattimerkkijonon syntaksi on kuitenkin C#:ssa erilainen kuin C:ssa (ja POSIXissa), joten siitä ei puhuta enempää nyt - selvitä itsellesi aina, miten formatoitu tulostus toimii siinä ympäristössä, jolla milloinkin ohjelmoit... Se on aina tavattoman kätevä tapa hoitaa tulostukset!
- Käytännön erot syntaksissa olivat siis tässä tapauksessa pieniä, eikä suuria ole odotettavissakaan, vaikka havaittaneen, että C:n lähdekoodi on toisaalta rajoitetumpaa ja toisaalta ehkä osin "kryptisempää" kuin C#:ssa tai Javassa. Luonnollinen syy tälle tilanteelle on, että uudempien kielten määrittelyssä voi aina yrittää oppia virheistä tai epäselvyyksistä aiempien kielten kohdalla. C# on uudempi kuin Java, joka on uudempi kuin C (joka on uudempi kuin B, joka on uudempi kuin BCPL, ...). Vaikka C-kielen uusin versio on vain pari vuotta vanha, 1970-luvulla tehtyjä "virheliikkeitä" ei voi kumota, koska vanhan koodin pitää edelleen kääntyä ja toimia uudessa versiossa.
Tähän astisen tarkoitus oli saada mahdollinen "C-pelkokerroin" putoamaan välittömästi: Kyseessä on suurelta osin tuttu asia, jos osaat jonkin verran ohjelmoida esimerkiksi C#:lla tai Javalla, kuten esitietokurssin Ohjelmointi 1 pohjalta tulisi olla asia. Eikä hätiä mitiä toisinkaan päin: Moni asia, mikä nyt saattaa tuntua uudelta ja ihmeelliseltä C:ssä, on itse asiassa hyvää kertausta ja vahvistusta Ohjelmointi 1:n asioihin ohjelmoinnin perusasioista!
Kuitenkin yksi merkittävä ero on nähtävissä taulukon ja merkkijonojen käsittelyssä:
- Javassa, C#:ssa ja muissa oliokielissä taulukko (esim. tässä args) on viite olioon, jonka rajapinnassa on attribuutti / ominaisuus, jonka kautta voi kysyä olioinstanssin pituutta eli elementtien määrää. Javassa attribuutti on nimeltään length ja C#:ssa ominaisuus on nimeltään Length.
- C:ssä ei ole kielen tasolla olemassa luokkia, eikä taulukon pituutta voi saada ominaisuuden arvona tai metodikutsulla. Siksi taulukon pituus tulee pääohjelman osalta erillisessä kokonaislukumuuttujassa, tyypillisesti nimeltään argc eli oletettavasti "argument count". Taulukko puolestaan on pötkö dataa, joka alkaa muistiosoitteesta nimeltään argv eli oletettavasti "argument vector".
- Merkkijonot puolestaan ovat merkkitaulukoita eli pötköjä, joita käsitellään ensimmäisen merkin osoitteella. Tässä siis argv on osoite, josta löytyy peräkkäin lisää osoitteita. Tyyppi on tämän vuoksi char ** eli "merkin muistiosoitteen osoite". Asiaan palataan tarkemmin seuraavassa demossa ja myöhemmissä luentoesimerkeissä.
Argumenttien, eli komentoriviltä annettavien, välilyönneillä erotettujen, merkkijonojen lisäksi jokainen suoritettava ohjelma saa käyttöönsä ympäristön (engl. environment). POSIX määrittelee standardin muodon ympäristölle, mutta sama periaate toteutuu myös esimerkiksi Windowsissa. Winkkari-puolella ohjelmien toimintaan voi vaikuttaa myös asetuksilla järjestelmänlaajuisessa ns. "rekisterissä". Toimintaidealtaan kaikki nämä tavat ovat hyvin samankaltaisia.
POSIXin määrittelemä ympäristö koostuu ympäristömuuttujista (engl. environment variable). Jokainen ympäristömuuttuja on merkkijonopari: Toinen merkkijono määrittelee nimen (eli "avaimen"), jonka kautta sisältöä käsitellään. Toinen merkkijono puolestaan on ympäristömuuttujan arvo. POSIX ei määrittele kovin monia nimiä, joille yhteensopivan järjestelmän tulisi määritellä arvoja, mutta se mainitsee kevyesti tyypillisiä käytössä olevia nimiä, joita kannattaa käyttää lähinnä mainittuihin tarkoituksiin eikä mihinkään omiin "sooloiluihin".
Ympäristömuuttujia voi asettaa esimerkiksi shellissä, jolloin ne asettamisen jälkeen näkyvät kaikille ohjelmille, jotka käynnistetään samassa interaktiivisessa shell-sessiossa tai skriptissä. Yksi tyypillinen käyttötarkoitus shell-skriptille onkin hoitaa tietyn nimisiin ympäristömuuttujiin tietyt, paikallisen asennuksen tarpeiden mukaiset, arvot, ja käynnistää varsinainen ohjelma vasta sitten, kun sitä ohjaavat ympäristömuuttujat on asetettu. Myös etäkoneelle kirjautumisen yhteydessä yleensä suoritetaan tietty skripti, jossa voi asettaa ympäristömuuttujia jokaista käynnistyvää sessiota varten.
Kevään 2019 luennolla nähtiin esimerkki bashin komentokehotteen muokkaamisesta vaihtamalla ympäristömuuttujan PS1 arvoa. Kaikkiin Bashin sessioihin haluamansa muuttuja-asetukset voi laittaa suorakäyttökoneen ~/.bashrc -tiedostoon tekstieditorilla.
Esimerkin vuoksi kokeillaan asettaa kansainvälisyysasetukset siten, että ei tarvitse kärsiä puoliksi käännetyistä suomenkielisistä teksteistä eikä suomenkielisistä virheilmoituksista, joiden perusteella on hyvin vaikea löytää Internetistä ohjeita. Olemme kohtalaisen pieni kielialue. Englanniksi löytyy apuja helpommin. Suomennoksetkin vaikuttaisivat olevan jossain määrin keskeneräisiä ja epätäydellisiä (tilanne 2016).
POSIX määrittelee, että ohjelmia voi pyytää toimimaan ympäristön paikallisuuden eli "lokaalin" (engl. locale) mukaisesti eri osa-alueiden osalta, jotka tyypillisesti ovat erilaisia eri kulttuureissa - mm. päivämäärät ja kellonajat, desimaaliluvut ja rahamäärät saatetaan kirjoittaa vaihtelevilla käytänteillä. Kuinka hyvin ohjelmat sitten tukevat erilaisia lokaaleja, on laatukysymys. Kääntäminen monelle kielelle on työlästä elikkäs kallista hommaa.
Virheilmoitusten saaminen englanniksi on helpointa saada aikaan asettamalla samalla kertaa kaikki kieliasetukset esimerkiksi yhdysvaltain muotoon. Käytännössä täytyy asettaa ympäristömuuttuja nimeltään LC_ALL arvoon en_US.utf8. Suorakäyttökoneillamme tämän voi hoitaa bash-sessiossa seuraavalla komennolla:
export LC_ALL="en_US.utf8"
(Konkretiaa haluaville: komento export on POSIXin määräämä, joten ''lähes yhteensopiva'' bash tuntee sen. Sillä julkaistaan ympäristömuuttuja näkymään kaikissa ohjelmissa, joita kyseisestä shell-sessiosta tai skriptistä jatkossa käynnistetään. Myös ympäristömuuttujan nimi LC_ALL on POSIXin määräämä. Sisältö kuitenkin voi olla vain sellainen kielimäärittely, joka löytyy juuri kyseiseen järjestelmään asennettuna. POSIX ei edellytä minkään maan kieliasetusten olemassaoloa. Asennetut kielet saa listattua komennolla locale -a, jonka antamasta listasta tuo kyseinen arvo en_US.utf8 on poimittu, sen sijaan että hatusta vedetty tai tuulesta temmattu olisi tässä meidän tapauksessamme.)
Samalla ikävä kyllä ilmoitustekstien lisäksi myös muut säädöt alkavat toimia jenkkienglannin mukaisesti - päivämäärät näyttävät hassuilta suomalaiseen silmään ja ääkkösiä sisältävien merkkijonojen lajittelu ei välttämättä vastaa suomalaista aakkosjärjestystä (voi mahdollisesti olla, että ä lajitellaan tasa-arvoisesti a:n kanssa ja ö tasa-arvoisesti o:n kanssa).
POSIX-standardi kertoo esimerkkien kautta hienojakoisemmista kieliasetuksista, missä käytetään useampaa ympäristömuuttujaa, nimiltään LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC ja LC_TIME. On esimerkiksi mahdollista pyytää viestit ranskaksi, merkkijonojen lajittelu saksaksi, desimaaliluvut suomeksi (eli pilkulla, ei pisteellä eroteltuna) ja päivämäärät yhdysvaltalaisittain, jos tällaisessa yhdistelmässä sattuisi olemaan käyttötarkoituksen kannalta jotakin järkeä. Jos tuo kaiken yliajava LC_ALL on asetettu, hienojakoisemmat säädöt eivät vaikuta.
Tässä harjoituksessa varmistetaan vähintään, että osaat säätää oman shell-session kaikki kieliasetukset englanniksi asettamalla LC_ALL -ympäristömuuttujan. Silloin osaat ylipäätään asettaa minkä tahansa ympäristömuuttujan sillä tavoin, että se näkyy kaikille suoritettaville ohjelmille.
Mikäli löydät itsellesi mieleisen kieliasetusten kombinaation, saat sen voimaan jokaiseen uuteen bashia käyttävään pääteyhteyteen editoimalla kotihakemiston tiedostoa .bash_profile lisäämällä sinne export-rivin. Kyseinen tiedosto suoritetaan automaattisesti silloin, kun interaktiivinen bash käynnistyy kirjautumisen yhteydessä. Tällöin ei tarvitse tehdä asetuksia aina uudelleen pääteyhteyssessioiden välillä.
Vastaavasti kotihakemiston tiedosto .bashrc näemmä suoritetaan jokaisen bash-käynnistyksen yhteydessä ja sitä kautta jokaisen skriptin alussa... Tälle voi olla tarkoituksensa, mutta täytyy huomata, että ympäristön normaali periytyminen lapsiprosessille menee silloin rikki noiden .bashrc:ssä tehtyjen muutosten kohdalla, jos käynnistyvä prosessi sattuu olemaan bash-skripti - ei kuitenkaan muunlaisten ohjelmien osalta. Voi tulla kummallisia ja odottamattomia ilmiöitä.
(Luennoitsija kampitti itsensä jollain vuosien takaisella "näppäryydellään" kevään 2015 kurssin viidennellä luennolla: Luennolla näytetty kieliasetuksen muutos bash-shellissä ei näyttänyt vaikuttavan suoritettavaan skriptiin, jossa date -komennon tuloste pysyi itsepintaisesti ja "maagisesti" suomalaisessa formaatissa, vaikka juuri oli asetettu jenkkienglannin mukaiset kieliasetukset LC_ALL -ympäristömuuttujalla. Magiikkaa näissä hommissa ei kuitenkaan koskaan ole, vaan syy oli joskus viimeisen 16 vuoden aikana lisätty rivi export LC_ALL=fi_FI.utf8 tiedostossa .bashrc. Ei mitään havaintoa, miksi tämä tuntui joskus hyvältä idealta lisätä. Nyt oli kuitenkin ihan hyvä aika poistaa se. Aa.. sylttytehdas alkaa löytyä jälkien perusteella.. Linkki Jyväskylä ry:n muinoisessa IRC-ohjeessa taidettiin puhua em. asetuksen laittamisesta .bashrc -tiedostoon.. jos muistais joskus jutella linkkareiden kanssa, niin tätä kohtaa ohjeesta voisi kaikkien näiden vuosien jälkeen tarkistaa...)
Pakollisessa palautustehtävässä verifioidaan, että olet onnistuneesti kääntänyt ja käynnistänyt C-kielisen ohjelman, antanut sille yhden argumentin, joka sisältää välilyönnin, ja asettanut ympäristömuuttujan avulla kieliasetukset ainakin siinä shell-sessiossa, jossa ajat kääntämäsi ohjelman.
Vastaustiedosto tuotetaan kääntämällä ja ajamalla alussa mainitusta URLista haettu C-lähdekoodi nimeltään d03_fiilikset.c siten, että tuloste ohjautuu shellissä vastaustiedostoon unix-rivinvaihtoineen aiemmissa demoissa opitulla syntaksilla, jossa käytetään väkästä >. Ei mitään copy-paste -kikkailuja tai "tarpeettomia echo-komentoja" edellenkään. Tulosteen pitäisi näyttää seuraavan malliselta (paitsi käyttäjätunnus on tietenkin omasi eikä nieminen, tuloste vastaa omia tunnelmiasi kurssilla jne..):
Ympäristömuuttuja USER == nieminen Ympäristömuuttuja HOME == /nashome3/nieminen Ympäristömuuttuja PWD == /nashome3/nieminen/kj15_esimerkkidemot/demo3 Ympäristömuuttuja LANG == en_US.utf8 Ympäristömuuttuja LC_ALL == en_US.utf8 argv[0] == ./a.out argv[1] == Ihan kivalta tuntuu: uskokaa tai älkää, niin aikataulu pitää aiempaa paremmin. Kertauksena: käyttäjän 'nieminen' tunnelmat tässä vaiheessa kurssia: Ihan kivalta tuntuu: uskokaa tai älkää, niin aikataulu pitää aiempaa paremmin.
Kieliasetusten täytyy olla ohjelmaa ajettaessa joko britti- tai jenkkienglanti UTF8-merkistökoodauksella.
Keväällä 2020 kurssin demot ja niiden palautus hoidetaan erillisessä järjestelmässä. Ohjeistettu luennoilla.
Tästä demosta palautetaan tasan yksi tiedosto nimeltään "d3_vastaus.txt", joka on luotu ohjaamalla C-ohjelman tuloste kyseiseen tiedostoon.