Konekielisessä ohjelmoinnissa hyödyllisiä standardikirjastofunktioita
TIES448 Kääntäjätekniikka, kevät 2017
Huom! Jos gcc valittaa kirjastofunktioita käytettäessä jotain seuraavanlaista (esim. uudehkoissa Ubuntuissa):
relocation R_X86_64_PC32 against symbol `GC_malloc' can not be used when making a shared object; recompile with -fPIC
Lisää gcc:n komentoriville -no-pie
. Se saattaa auttaa.
C-tyyppien tulkintaa
Tyyppi | AMD64 SysV ABI | ARM EABI |
---|---|---|
_Bool |
1 | 1 |
char |
1 | 1 |
short |
2 | 2 |
int |
4 | 4 |
long |
8 | 4 |
long long |
8 | 8 |
float |
4 | 4 |
double |
8 | 8 |
long double |
16 | 8 |
size_t |
unsigned long |
unsigned int |
Taulukossa esitetty luku kertoo kyseisen tyypin koko tavuina; jos luvun sijasta mainitaan toisen tyypin nimi, kyse on synonyymistä. Kunkin taulukossa mainitun tyypin muuttujan osoitteen tulee olla jaollinen tyypin koolla.
Tyyppien char
, short
, int
, long
ja long long
eteen voidaan kirjoittaa avainsana unsigned
tai signed
merkitsemään, tulkitaanko luku moduloaritmetiikan vai kahden komplementin mukaisesti. AMD64 SysV ABI määrittelee, että char
ilman lisämäärettä tulkitaan kahden komplementin mukaisesti, mutta ARM EABIssa char
ilman lisämäärettä tulkitaan moduloaritmetiikan mukaisesti. Kaikki muut tässä kappaleessa mainitut tyypit ilman lisämäärettä käytettynä tulkitaan kahden komplementin mukaisesti.
Osoitin tyypin T
muuttujaan on C:ssä tyypiltään T *
. Sen koko on 8 (AMD64 SysV ABI) tai 4 (ARM EABI) tavua. Osoittimen osoitteen tulee olla jaollinen tällä kokoluvulla.
Joskus käytetään osoitintyyppiä void *
. Tämä tarkoittaa osoitinta, jonka kohteen tyyppiä ei sanota ääneen.
C-standardissa esiintyy tyyppi FILE
, mutta koskaan et joudu luomaan sen tyyppistä muuttujaa, sillä aina käsittelet vain tyyppiä FILE *
(joka on osoitin).
C-kielessä merkkijonot esitetään perinteisesti osoittimena char
-taulukon alkuun (ja ovat siten tyypiltään char *
). Merkkijonon viimeisen merkin perässä tulee olla nollatavu.
Merkkien luokittelua
int isalnum(int);
int isalpha(int);
int isblank(int);
int iscntrl(int);
int isdigit(int);
int isgraph(int);
int islower(int);
int isprint(int);
int ispunct(int);
int isspace(int);
int isupper(int);
int isxdigit(int);
Nämä kaikki funktiot ottavat parametrinaan unsigned char
-tyypin arvon, joka on konvertoitu int
-tyypin arvoksi (lukuarvo säilyttäen), ja palauttavat nollasta eroavan luvun, jos ehto täytyy:
isalnum
: merkki on kirjain tai numeroisalpha
: merkki on kirjainisblank
: merkki on sanojen erottamiseen rivillä sopiva tyhjä merkki (esim. välilyönti tai tabulaattori) mutta ei rivinvaihto eikä mikään sellaineniscntrl
: merkki on kontrollimerkkiisdigit
: merkki on numeromerkkiisgraph
: merkin voi tulostaa järkevästi mutta se ei ole välilyöntiislower
: merkki on pieni kirjainisprint
: merkin voi tulostaa järkevästiispunct
: merkki on välimerkkiisspace
: merkki on tyhjä merkki (esim. välilyönti tai rivinvaihto)isupper
: merkki on iso kirjainisxdigit
: merkki on heksadesimaalinen numeromerkki
Merkkien muokkausta
int tolower(int);
int toupper(int);
Nämä kaikki funktiot ottavat parametrinaan ja palauttavat unsigned char
-tyypin arvon, joka on konvertoitu int-tyypin arvoksi (lukuarvo säilyttäen). Ne muuttavat pienet isoiksi (toupper
) tai isot pieniksi (tolower
) kirjaimiksi. Jos argumentille ei voi tehdä tällaista muunnosta, se palautetaan sellaisenaan.
Syöttö ja tulostus
Tiedoston avaaminen ja sulkeminen
FILE *fopen(const char *filename, const char *mode);
int fclose (FILE *);
Tiedosto avataan fopen
-funktiolla, jolle annetaan parametrina tiedoston nimi. Moodiparametrin tulee alkaa jollakin seuraavista kirjaimista:
r
- avataan lukemista vartenw
- avataan tyhjän tiedoston alkuun kirjoittamista varten (tarvittaessa luodaan tiedosto tai tyhjennetään olemassaoleva)a
- avataan tiedoston nykysisällön perään kirjoittamista varten (tarvittaessa luodaan tiedosto)
Tämän perään voidaan lisätä yksi tai useampi seuraavista merkeistä (mikäli käytät useampia, laita ne tähän järjestykseen):
+
- sekä lukeminen että kirjoittaminen on mahdollistab
- tiedoston sisältö luetaan sellaisenaan (ilman tätä tiedoston katsotaan olevan tekstitiedosto ja esimerkiksi rivinvaihtomerkkejä käsitellään joissakin järjestelmissä eri tavalla)x
- tiedosto luodaan nyt ja lukitaan muita ohjelmia vastaan (jos tiedosto on olemassa tai lukitseminen epäonnistuu, operaatio epäonnistuu)
Linuxissa ja muissa POSIX-yhteensopivissa järjestelmissä b
ei tee mitään. Lisäksi x
on uusi lisäys eivätkä kaikki järjestelmät ymmärrä sitä.
Funktion fopen
paluuarvo on osoitin. Jos se on nolla, operaatio epäonnistui. Muussa tapauksessa osoittimen sisältöä ei tule tarkastella lähemmin, mutta sitä voi käyttää tiedostonkäsittelyssä.
Jokainen fopen
-funktiolla avattu tiedosto tulee sulkea fclose
-funktiolla. Se palauttaa nollan jos sulkeminen onnistui.
Vakiovirrat
Komentoriviohjelman syöte (esimerkiksi käyttäjän näppäimistö) on valmiiksi avattu lukemista varten ja sitä voi käyttää nimellä stdin
. Sitä ei tarvitse sulkea itse.
Komentoriviohjelman tuloste (esimerkiksi terminaali-ikkuna) on valmiiksi avattu kirjoittamista varten ja sitä voi käyttää nimellä stdout
. Sitä ei tarvitse sulkea itse.
Komentoriviohjelman virheilmoituksia varten (jotka menevät lähtökohtaisesti siihen terminaali-ikkunaan, jossa ohjelma käynnistettiin, vaikka tuloste olisikin ohjattu tiedostoon) on avattu valmiiksi kirjoittamista varten ja sitä voi käyttää nimellä stderr
. Sitä ei tarvitse sulkea itse.
Valitettavasti näiden käyttäminen suoraan assemblystä ei ole useinkaan mahdollista, sillä stdin
, stdout
ja stderr
saavat olla C-kielen makroja. Ainakin Ubuntu 16.10:ssä käyttöyritys aiheuttaa kummallisia virheilmoituksia.
Lukeminen ja kirjoittaminen merkki kerrallaan
int fgetc(FILE *);
int fputc(int, FILE *);
int getchar();
int putchar();
Merkki luetaan annetusta tiedostosta fgetc
-funktiolla. Funktio getchar
lukee stdin
istä.
Merkki kirjoitetaan annettuun tiedostoon fputc
-funktiolla. Funktio putchar
kirjoittaa stdout
iin.
Kaikissa näissä funktioissa unsigned char
-tyyppinen arvo on muutettu int
-tyyppiseksi arvo säilyttäen (eli nollabittejä lisäten).
Kaikki nämä funktiot palauttavat virhetilanteessa negatiivisen luvun. Lukufunktiot palauttavat negatiivisen luvun myös silloin, kun tiedoston loppu on saavutettu.
Formatoitu kirjoittaminen (printf
)
int printf(char *fmt, ...);
int fprintf(FILE *, char *fmt, ...);
Kumpikin funktio tulostaa fmt
-merkkijonon, jossa %
:llä alkavat ilmaisut korvataan muiden argumenttien merkkijonotulkinnalla. Funktio printf
tulostaa stdin
iin ja funktio fprintf
annettuun tiedostoon.
Muutamia hyödyllisiä %
-ilmaisuja (katso C-kielen dokumentaatiosta lisää):
%s
- tulosta argumenttina olevachar *
-tyyppinen merkkijono sellaisenaan.%d
- tulosta argumenttina olevaint
-tyyppinen luku%lf
- tulosta argumenttina olevadouble
-tyyppinen luku
Esimerkiksi kutsu
const char * nimi = "Antti-Juhani";
int ika = 39;
printf("Terve %s, ikäsi on %d vuotta\n", nimi, ika);
tulostaa
Terve Antti-Juhani, ikäsi on 39 vuotta
ja rivinvaihdon.
Kumpikin funktio palauttaa tulostettujen merkkien määrän, mutta virhetilanteessa negatiivisen luvun.
Formatoitu luku (scanf
)
int scanf(char *fmt, ...);
int fscanf(FILE *, char *fmt, ...);
int sscanf(char *input, char *fmt, ...);
Kumpikin funktio lukee fmt
-merkkijonon ohjauksessa syötettä. Funktio scanf
lukee stdin
istä ja fscanf
annetusta tiedostosta. Funktio sscanf
lukee annettua input
-merkkijonoa.
Merkkijonossa fmt
jokainen tyhjä merkki (esim. välilyönti tai rivinvaihto) tarkoittaa, että seuraavat tyhjät merkit syötteestä ohitetaan. Jokainen ei-tyhjä merkki seuraavaan %
-merkkiin asti tulee löytyä sellaisenaan syötteestä. Merkki %
aloittaa muunnoskäskyn. Muunnoskäskyjä ovat mm. seuraavat (lue C-kielen dokumentaatiosta tarkemmin):
%d
- lue kokonaisluku ja tallenna se seuraavan argumentin (jonka tulee olla tyyppiäint *
) osoittamaan muuttujaan%lf
- lue liukuluku ja tallenna se seuraavan argumentin (jonka tulee olla tyyppiädouble *
) osoittamaan muuttujaan
Funktiot palauttavat, kuinka monen argumentin osoittamaan muuttujaan tallennettiin tavaraa. Mikäli lukemisessa sattui virhe ennen ensimmäistä tallennusta, palautetaan negatiivinen arvo.
Huom! Näitä funktioita on syytä välttää oikeassa ohjelmoinnissa, mutta niillä on varsin helppo tehdä kokeilukäyttöön soveltuvia ohjelmia.
Muistinvaraus
Manuaalisella muistin vapautuksella
void *aligned_alloc(size_t alignment, size_t bytes);
void *malloc(size_t bytes);
void *realloc(void *, size_t bytes);
void free(void *);
Funktio malloc
varaa keosta pyydetyn määrän tavuja yhtenäisenä muistialueena ja palauttaa osoittimen sen alkuun.
Funktio aligned_alloc
varaa keosta pyydetyn määrän tavuja yhtenäisenä muistialueena, jonka osoite on jaollinen alignment
-luvulla. Se palauttaa osoittimen varatun muistialueen alkuun.
Funktio realloc
muuttaa aiemmin varatun muistialueen kokoa tuhoamatta sen sisältöä. Huomaa, että muistialueen osoite voi muuttua tämän seurauksena; realloc
palauttaa uuden osoitteen. Mikäli realloc
ille vie nollaosoittimen, se toimii kuten malloc
.
Funktiot aligned_alloc
, malloc
ja realloc
palauttavat nollaosoittimen, jos muistinvaraus epäonnistui. Jos realloc
palauttaa nollaosoittimen, alkuperäinen muistialue on edelleen käytössä alkuperäisellä koollaan.
Varatun uuden muistin sisältö voi olla mitä vain.
Jokainen aligned_alloc
in, malloc
in tai realloc
in palauttama osoite tulee vapauttaa kerran ja vain kerran.
Osoite vapautetaan antamalla se argumenttina free
lle. Tämän jälkeen kyseistä osoitetta ei enää saa käyttää.
Mikäli realloc
palauttaa muuta kuin nollaosoittimen, on se vapauttanut sille annetun osoitteen ja varannut sen palauttaman osoitteen. Sille annettua osoitetta ei saa uudestaan antaa realloc
ille eikä free
lle, ja sen palauttama osoite tulee vapauttaa.
Osoitteen vapauttaminen kahdesti taikka osoitteen käyttäminen sen jälkeen, kun se on vapautettu, on vakava bugi joka voi johtaa ohjelman arvaamattomaan käytökseen tai ohjelman kaatumiseen. Se voi myös avata tietoturva-aukkoja, jos ohjelma on tietoturvan kannalta merkityksellinen. Vapauttamatta jättäminen puolestaan aiheuttaa muistivuodon, joka voi pahimmillaan estää tietokoneen käytön, kaataa ohjelman tai estää enemmän muistin varaamisen.
Roskienkeruulla
Roskienkeruu (garbage collection) ei kuulu standardikirjastoon, mutta koska siitä on paljon hyötyä, esittelen sen tässä. Tarvittava kirjasto on Boehm–Demers–Weiser, ja sen asentamisesta en kirjoita tässä mitään. Ohjelmaan tulee linkittää kyseinen kirjasto (GCC:llä komentorivioptio -lgc
)
void *GC_malloc(size_t);
void *GC_malloc_atomic(size_t);
void *GC_malloc_ignore_off_page(size_t);
void *GC_realloc(void *, size_t);
Nämä kaikki funktiot varaavat roskienkeruun alaisuuteen kuuluvan yhtenäisen muistialueen, jonka koko on pyydetynlainen. Muistialue säilyy hengissä vähintään niin kauan kuin siihen on rekistereissä, pinossa, globaaleissa muuttujissa tai muissa roskienkeruun alaisuuteen kuuluvissa muistialueissa ainakin yksi osoitin. Muistialue tuhotaan automaattisesti joskus sen jälkeen, kun viimeinen siihen osoittava osoite poistuu.
Funktiolla GC_malloc_atomic
varatusta muistialueesta ei etsitä osoittimia muihin roskienkeruun alaisiin muistialueisiin. Sitä kannattaa käyttää aina, kun muistialueeseen ei ole tarkoitus tallettaa osoittimia.
Funktiolla GC_malloc_ignore_off_page
varattuun muistialueeseen tulee olla ainakin yksi osoitin, joka osoittaa kyseisen muistialueen alkuosaan (ensimmäiset 256 tavua), mikäli muistialueen halutaan säilyvän hengissä. Muualle kyseiseen muistialueeseen osoittavat osoittimet jätetään huomiotta. Tätä kannattaa käyttää isojen muistialueiden varaamiseen, mutta huomaa osoittimen käyttörajoitus.
GC_realloc
muuttaa aiemmin roskienkeruun alaisuuteen varatun muistialueen kokoa muuttamatta sen sisältöä. Muistialueen osoite voi sen sijaan vaihtua; uusi osoite palautetaan funktion paluuarvona.
These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.