četvrtak, 18 aprila, 2024
Kako da...?

Uvod u programski jezik C (10. deo)

Autor: Nikola Hardi

Uvod u rad sa datotekama

U prethodnom delu serijala, započeto je razmatranje rada sa tekstom u programskom jeziku C. Nadamo se da smo uspeli da predstavimo značaj ove teme. U ovom nastavku serijala biće predstavljen rad sa datotekama. Tok rada klasičnog programa se uglavnom deli na tri faze: učitavanje podataka, obrada, ispis rezultata. U sličnim tekstovima uglavnom je akcenat na samoj obradi. Unos podataka i čuvanje rezultata su jednako važne teme.

Pristupanje datotekama

Prvi korak u radu sa datotekama je „otvaranje” datoteke. Šta to zapravo znači? Bez previše priče o operativnim sistemima, standardnoj biblioteci i sprezi između korisničkih programa i operativnog sistema, cela ideja se svodi na to da program od operativnog sistema traži pristup nekoj datoteci putem mehanizma sistemskih poziva. Operativni sistem potom programu obezbeđuje apstraktnu predstavu te datoteke u vidu toka bajtova, bez potrebe da korisnički program i programer poznaju sistem datoteka (eng. filesystem) ili uređaj o kojem je reč.

Datoteci je moguće pristupiti na više nivoa, pozivom funkcije open() koja se direktno preslikava u sistemski poziv open. Pozivom ove funkcije, operativni sistem korisničkom programu obezbeđuje deskriptor datoteke (eng. file descriptor) koji je u suštini evidencioni broj otvorene datoteke na nivou sistema. Dakle, reč je o običnoj celobrojnoj vrednosti. Rad sa datotekama ovim mehanizmom se smatra radom na niskom nivou, a standardna biblioteka jezika C obezbeđuje i druge mehanizme koji se takođe oslanjaju na sistemski poziv open, sa još nekoliko dodataka.

Drugi način, koji i preporučujemo, je pomoću strukture FILE i funkcije fopen(). Razlog za to je što upotrebom ove ugrađene strukture podataka imamo mogućnost da koristimo i druge standardne funkcije za rad sa datotekama koji će biti opisani.

Datoteke mogu biti otvorene u više režima kao što su režim za čitanje, režim za pisanje, režim za dodavanje sadržaja i kombinacija prethodnih. Ponekad se u literaturi sreću i oznake za rad sa binarnim datotekama, ali one se na većini modernih sistema jednostavno ignorišu.

Sledi primer otvaranja datoteke na oba prethodno opisana načina, a potom i objašnjenje o režimima u kojima je datoteka otvorena.

#include  #include  int main() { int fd = open("my_file.txt", O_WRONY | O_CREAT, S_IRUSR | S_IWUSR ); printf("%d\n", fd); write(fd, "LiBRE!\n", 7); close(a); return 0; }

Kao što možete da vidite, ovo izgleda malo nezgrapno. Funkcija open() ima mnogo namena, pa je zbog toga njena upotreba i složenija. Prvi argument je naziv (tačnije, putanja do datoteke). Sledeći parametar su podešavanja za režim otvaranja datoteke (samo čitanje i kreiranje datoteke ukoliko ona već ne postoji). Treći parametar su podešavanja za dozvole datoteka (eng. permissions), koja u o ovom slučaju podrazumeva da samo trenutni korisnik ima pravo pisanja i čitanja. Sledeći izraz je ispisivanje deskriptora datoteka koji bi trebao da bude pozitivan broj ukoliko je otvaranje datoteke bilo uspešno. Nakon toga, vrši se upis teksta “LiBRE!\n”, čija je dužina 7 u datoteci sa deskriptorom datoteka fd. Po završetku rada sa datotekom, dobro je zatvoriti je pozivom funkcije close(), jer tek tada možemo biti sigurni da će operativni sistem zapisati sav sadržaj na disk. U biblioteci su definisani simboli koji su korišćeni za podešavanje režima i kreiranje datoteke.

Više detalja je dostupno u man stranicama kojima je moguće pristupiti komandom u terminalu: man 2 open. Druge korisne man stranice za rad sa datotekama su write, read, kao i stranice za ostale funkcije koje će biti opisane. Funkcije na višem nivou apstrakcije se nalaze u trećem delu priručnika, dakle: man 3 fscanf.

Nešto ugodniji način rada sa datotekama je pomoću funkcija fopen(), fwrite(), fread() itd. Oni pružaju viši nivo apstrakcije i potrebno je manje koda, a i pažnje da bi sve proradilo kako treba. Važno je poznavati i mehanizme na koje se te funkcije naslanjaju, a to su upravo pomenuti sistemski pozivi.

Sledi primer koda u kojem se pristupa datoteci kreiranoj u prethodnom primeru.

#include  int main() { char buffer[128]; FILE *f = fopen("my_file.txt", "r"); fscanf(f, "%s", buffer); fclose(f); puts(buffer); return 0; }

Ovaj kôd je znatno čitljiviji. Nema kriptičnih parametara ako ništa drugo. Funkcija fopen() ima za povratnu vrednost adresu kreirane FILE strukture, a ukoliko otvaranje datoteke iz nekog razloga nije bilo uspešno, ta adresa je 0. Poželjno je proveriti da li je otvaranje bile uspešno. Parametri ove funkcije su putanja do datoteke i režim pristupanja. Postojeći režimi su:

  • r – samo čitanje
  • r+ – čitanje i pisanje od početka datoteke
  • w – samo pisanje
  • w+ – pisanje i čitanje, ako datoteka postoji briše se
  • a – pisanje na kraj datoteke
  • a+ – pisanje na kraj i čitanje

Režimi w i a će kreirati novu datoteku ukoliko ona već ne postoji.

Opširnija dokumentacija je dostupna u man stranicama.

Ostale funkcije

Standardna biblioteka sadrži mnogo funkcija koje rad sa datotekama čine jednostavnijim i ugodnijim. Među njima su čitanje jednog karaktera, čitanje stringa, čitanje linije, čitanje zadatog broja bajtova itd. Sve te funkcije imaju i svoje parove za pisanje podataka. Nazivi ovih funkcija počinju slovom f, a vama prepuštamo da pogodite njihove nazive i potražite detalje u man stranicama.

Rad sa datotekama ne podrazumeva i rad sa tekstom. Moguće je čuvati i tzv. binarne podatke, kao što su strukture, fotografije i sl. Sve što je potrebno je odrediti koliko bajtova je potrebno učitati ili zapisati, zadati sa kojom datotekom se radi i gde sačuvati rezultat, odnosno odakle pročitati podatke za upis. Veličinu strukture je moguće saznati primenom operatora sizeof ili je jednostavno izračunati unapred.

Kod rada sa datotekama, kao i u mnogim drugim situacijama, javljaju se greške: nema dovoljno prostora na disku, program nema dozvolu za pristupanje zadatoj datoteci, u datoteci ne postoje zadati podaci itd. Zbog toga je važno proveravati povratne vrednosti funkcija i protumačiti njihovo značenje na osnovu sadržaja man stranica.

Nekoliko reči o separatorima

Kada je reč o radu sa tekstualnom datotekom, javlja se koncept reda, odnosno linije teksta. Pošto su datoteke samo nizovi digitalnih podataka, računari ne poznaju koncept reda teksta. Redovi su apstrakcija poznata ljudima, odnosno korisnicima računara, i zbog toga su veštački uvedeni pojedinim standardima. Standard ASCII propisuje vrednosti za više specijalnih znakova, kao što su, između ostalog, nov red, povratak glave štampača na početak reda, uključivanje zvona na znakovnom terminalu ili štampaču itd. Kroz istoriju su se javila dva standarda za označavanje kraja reda teksta, među programerima poznata kao DOS i Juniks (eng. Unix) standardi.

U DOS i Vindouz svetu se kraj reda označava dvama karakterima – \r i \n. Ova konvencija je prisutna iz istorijskih razloga i bila je prisutna u doba linijskih štampača kojima je zadavana eksplicitna naredba za prelazak u nov red i vraćanje glave na početak reda. U Juniks svetu je to samo jedan znak, \n. Zbog toga su mogući problemi pri razmeni datoteka sa kolegama koje koriste drugačije operativne sisteme. Simptom je jednostavan – sadržaj cele datoteke je u jednom redu ili postoje čudni znakovi na kraju svakog reda. Na Linuksu su dostupna dva jednostavna programa koja rešavaju ovu zbrku, a to su dos2unix i unix2dos. Programi za uređivanje teksta, takođe, mogu da budu podešeni za rad u jednom ili drugom režimu. Obratite pažnju na ovaj problem jer je prisutan i dan-danas.

Pozicioniranje unutar datoteke

Često postoji potreba da se program pozicionira na drugu lokaciju u datoteci. Ovaj mehanizam je prisutan još iz doba kada su datoteke bile smeštane na magnetnim trakama, pa prema tome i funkcije imaju sličan naziv, odnosno fseek() i rewind(), što bi na našem jeziku značilo „premotati”. Premotavanje može da bude unapred ili unazad. Referentne tačke mogu da budu od početka datoteke, od trenutne pozicije, ili od kraja datoteke. Postoji i funkcija ftell() koja govori trenutnu poziciju unutar datoteke. Funkcija rewind() „premotava” datoteku na njen početak.

Ne izmišljajte toplu vodu

Moderni informacioni sistemi, baze podataka, pa i računarska tehnika donekle, potekli su jednim delom iz miljea korporacija koje su se bavile razvojem mašina za rad sa dosijeima. Reč datoteka (eng. file) je prvenstveno predstavljala dosije u kartoteci ili sličnoj službi, a posao računara je jednim delom bio da brzo pronađe ili pohrani podatke u odgovarajući dosije. Od koverti sa dosijeima prešli smo na koncept datoteka, a u raznim fazama razvoja računarskih nauka došli smo do koncepta baza podataka. Teorija baza podataka i informacionih sistema nisu tema ni ovog teksta, a ni časopisa; ali važno je napomenuti da ne treba izmišljati toplu vodu.

Ukoliko postoji potreba za čuvanjem tabelarnih podataka, standardni način je CSV (eng. Comma Separated Value), odnosno vrednosti razdvojene zapetama. U stvarnosti, vrednosti mogu da budu razdvojeni i drugačijim znacima (engl. delimiterima), razmacima, zvezdicama, uspravnim crtama itd. Iako je za C česta praksa da se kôd za učitavanje ovakvih podataka piše u nekoliko linija koda, svaki put – mnogi drugi jezici i okruženja za programiranje nude automatizovane alate i biblioteke za ovaj posao. Savetujemo da se taj format ispoštuje.

Ukoliko postoji potreba za pohranjivanjem većeg broja podataka u više datoteka, a podaci su pritom i uvezani, onda je to posao već za jednu bazu podataka. Sistemi za upravljanje bazama podataka sa jedne strane programeru pružaju spregu za pristup podacima na apstraktnom nivou, a sa druge strane sami vode računa kako će to smestiti na disk. Uz to, pružaju i sjajne mehanizme za brzu pretragu, efikasno smeštanje podataka i pribavljanje odgovora na upite. Postoje relacione i nerelacione baze podataka, one za koje su potrebni serveri i one koje rade u memoriji. SQLite je biblioteka i koncept baze podataka za koju nije potrebno podešavati server, kreirati korisničke naloge i instancirati pojedinačne baze. Baze se jednostavno čuvaju u jednoj datoteci koja je organizovana na specifičan način. Vredi pogledati, a svakako će o ovoj temi biti reči u nekom od narednih brojeva.

Poliglote i šizofreničari

Ponekad, ipak, postoji potreba za definisanjem novog tipa datoteke. Tako su propisani formati datoteka kao što su ZIP, PDF, ELF, JPEG itd. Dobro je pitanje i ogroman problem kako pravilno opisati strukturu jedne datoteke, kako prepoznati tip datoteke i proveriti da li je struktura validna i, još gore, kako to pretočiti u funkcionalan program. Poznati su bezbednosni problemi koji se baziraju upravo na ovim problemima. Zlonamerni programi mogu biti sakriveni unutar druge datoteke, recimo unutar PDF dokumenta, ili više programa može biti spojeno unutar jedne izvršne datoteke. Otuda i naziv ovog odeljka – poliglotama se smatraju datoteke koje mogu biti protumačene u više formata, recimo PDF i fotografija. Šizofreničari su datoteke koje svoj sadržaj prikazuju drugačije u zavisnosti od različitih programa za pregledanje – naprimer, Evince prikazuje PDF sa jednim sadržajem, a Okular PDF sa drugim sadržajem.

Ovom temom se bavi Ange Albertini. On je na ovogodišnjem CCC-u (eng. Chaos Communication Congress) održao predavanje u kojem je opisao ove probleme. Predavanje je dostupno na sajtu http://media.ccc.de pod nazivom „Funky file formats”. Neke od ovih tehnika su demonstrirane u časopisu PoC||GTFO, koji je jednostavno pronaći na internetu. U njima su opisani i detalji o načinu na koji je moguće napraviti ovakve datoteke. Na sajtu http://www.corkami.com postoje, između ostalog, i posteri koji opisuju strukturu nekih poznatih tipova datoteka. Predlažemo da pogledate o čemu je reč. Oformila se i posebna grupa istraživača koji se donekle bave ovom temom, a reč je o temi pod nazivom LANGSEC (http://langsec.org/).

U narednom tekstu

Narednim tekstom će ovaj serijal polako biti priveden kraju. Obrađene su najvažnije teme ovog programskog jezika. Vremenom su tekstovi neplanirano porasli, ali ne zamerite. U narednom tekstu (najverovatnije će to biti više od jednog teksta), biće opisane ideje koje prevazilaze sam jezik, smernice šta zanimljivo može da se napravi, korisne biblioteke i drugi važni koncepti. Spremni ste da napravite i svoj prvi program koji ćete moći da koristite svakodnevno. Javite nam se ukoliko želite da obradimo neku temu detaljnije ili da dodamo još koji tekst u serijal.

Naredni deo ovog serijala možete pročitati ovde.

Prethodni deo ovog serijala možete pročitati ovde.