C je razvio Dennis Ritchie u periodu od 1969. do 1973. godine u Bell Labs, za potrebe reimplementacije Unix operativnog sistema. Danas je jedan od najčešće korišćenih programskih jezika. Poseduje C kompajlere različitih proizvođača i dostupan je na svim glavnim platformama i operativnim sistemima. Za njegovu standardizaciju se brine Američki Nacionalni Institut za Standarde (ANSI) i kasnije Internacionalna Organizacija za Standardizaciju (ISO).
Kompajler je kompjuterski program (ili skup programa) koji transformiše izvorni kod napisan u nekom programskom jeziku (izvorni jezik) u drugi programski jezik, najčešće u binarni format (objektni kod). Razlog ovakve konverzije je kreiranje izvršnog programa. Ime “kompajler” se koristi za programe koji prevode izvorni kod iz programskih jezika visokog nivoa (c, c++, …) u jezike niskog nivoa (asembler, mašinski kod).
Prevođenje programa napisanog u programskom jeziku C u izvršni kod se izvodi u 4 koraka:
Za bilo koji ulazni fajl, sufiks imena fajla (ekstenzija fajla) određuje koja vrsta prevođenja je urađena. Primer za GCC kompajler je dat u tabeli ispod.
Ekstenzija fajla | Opis |
---|---|
ime_fajla.c | C izvorni kod koji mora da bude preprocesuiran |
ime_fajla.i | C izvorni kod koji ne treba da bude preprosesuiran |
ime_fajla.h | C heder fajl (ne treba da bude kompajliran i povezan) |
ime_fajla.s | Kod asemblera. |
ime_fajla.o | Objektni fajl. |
Na UNIX/Linux sistemima izvršni ili binarni fajl ne sadrži ekstenziju, dok na Windows sistemima ekstenzija može biti .exe, .com ili .dll.
Fajl primer1.c.
Proces prevodjenja C programa počinje sa preprocesuiranjem direktiva (npr. #include i #define). Preprocesor je odvojen program koji se automatski poziva tokom prevođenja. Na primer, komanda
na liniji 2 fajla primer1.c govori preprocesu da svako pojavljivanje makroa BROJ zameni sa brojem 3 (vrednost makroa). Rezultat je novi fajl (obicno sa ekstenzijom .i). U praksi, preprocesuiran fajl se ne pamti na hdd osim ako nije uključena opcija -save-temps. Ovo je prva faza procesa prevođenja gde preprocesor proširuje fajl. Da bi izvršio ovaj korak gcc kompajler interno pokreće komadnu
Rezultat je fajl primer1.i koji sadrži izvorni kod sa proširenim svim makroima. Ovako izvršena komanda pamti fajl primer1.i na hard disk.
U ovoj fazi kompajler prevodi fajl primer1.i u primer1.s izvršenjem komande
Fajl primer1.s sadrži asemblerski kod. Opcija komandne linije -S govori kompajleru da preprocesuirani fajl konvertuje u kod asemblerskog jezika bez kreiranja objektnog fajla. Dodatno objašnjenje komandi asemblerskog koda mogu se pronaći u dokumentaciji programa as.
Prevodilac dozvoljava da se, u kodu, referencira samo na promenljivu ili funkciju koja je prethodno deklarisana. Deklarisanje je obećanje da definicija (promenljive ili funkcije) postoji negde drugde u celom programu.
U ovom koraku montažer (assembler - as) prevodi primer1.s u jezik mašinskih instrukcija i genereiše objektni fajl primer1.o. Izvršenjem komande
se pokreće asembler. Rezultujući fajl primer1.o sadrži mašinske instrukcije C programa primer1.c. Zavisno od platforme na kojoj se kod prevodi objektni fajl može da bude kreiran u više formata. U tabli ispod su prikazani formati objektnog fajla.
Format objektnog fajla | Opis |
---|---|
a.out | a.out je format fajla za Unix. Sastoji se od tri sekcije: text, data i bss u kojima se nalazi programski kod, inicijalizovani podaci i neinicijalizovani podaci, respektivno. Ovaj format je jednostavan. Ne sadrži mesto rezervisano za informacije debagera. |
COFF | COFF (Common Object File Format) je uveden na System V Release 3 (SVR3) Unix-u. Može da sadrži više sekcija, koje imaju kao prefiks ime zaglavlja. Broj sekcija je ograničen. Sadrži podršku za debagovanje ali je količina informacija ograničena. |
ECOFF | Varijanta COFF-a. ECOFF je proširen (extended) COFF originalno koriščen na Mips i Alpha radnim stanicama. |
PE | Windows 9x i NT koriste PE (Portable Executable) format. PE je u suštini COFF sa dodatnim zaglavljima. |
ELF | ELF (Executable and Linking Format) je uveden na System V Release 4 (SVR4) Unix-u. ELF je sličan COFF formatu tako što je organizovan u sekcijama, ali ne postoje brojna COFF ograničenja. ELF se koristi u većini modernih Unix-olikih sistema, uključujući GNU/Linux, Solaris i Irix. |
Objektni fajl sadrži oblasti koje se nazivaju sekcije. U sekcijama može da se nalazi kod koji se izvršava, podaci, informacije za dinamičko povezivanje, informacije debagera, tabelu simbola, informacije za realokaciju, komentare, tabelu stringova i beleške. Neke sekcije se učitavaju u sliku procesa, neke sadrže informacije potrebne za kreiranje slike procesa, a neke se koriste samo za povezivanje objektnih fajlova. Određene sekcije se pojavljuju bez obzira na tip formata (moguće je da su drugačijeg imena, zavisno od kompajlera/linkera):
Sekcija | Opis |
---|---|
.text | Sadrži kod instrukcija koje se izvršavaju i svaki proces koji pokreće isti izvršni fajl koristi istu sekciju. Ova sekcija najčešće ima samo Read i Execute dozvolu. Mogućnost optimizacije je najveća u ovoj sekciji. |
.data | Sadrži inicijalizovane globalne i static promenljive i njihove vrednosti. Najčešće zauzima najveći deo memorije izvršnog procesa. Poseduje Read / Write dozvolu. |
.bss | BSS (Block Started by Symbol) je sekcija koja sadrži neinicijalizovane globalne i static promenljive. Pošto .bss sadrži samo promenljive koje još uvek nemaju vrednost nije potrebno , pre kreiranja procesa, rezervacija prostora za vrednosti promenljivih. Veličina koja je potrebna za ovu sekciju pri izvršavanju je upisana u fajlu objekta, ali .bss (za razliku od .data) ne zauzima nikakav prostor u objektnom fajlu. |
.rdata | Obeležava se i kao .rodata (read-only data) sekcija. Sadrži konstante i stringove. |
.reloc | Sadrži informacije potrebne za realociranje slike procesa tokom učitavanja. |
Symbol table | Simbol je u stvari ime i adresa. Tabela simbola sadrži informacije potrebne za lociranje i realociranje programskih simboličkih definicija i referenci. |
Relocation records | Relokacija je proces povezivanja simboličkih referenci sa simboličkom definicijom. Npr. kada program pozove funkciju, neophodno je pronaći odgovarajuću adresu gde se nalazi definicija te funkcije. Prostije rečeno slogovi realokacije su informacije koje linker koristi da prilagodi sadržaj sekcije. |
Sadržaj objektnog fajla moguće je prikazati pomoću readelf programa komandom
Ranije smo napomenuli da je deklaracija funkcije ili promenljive obećanje C prevodiocu da negde drugde u programu je definicija te funkcije ili promenljive i posao povezivača (linker) je da to obećanje ostvari. Da bi smo ostvarili obećanja o postojanju funkcije int fn_a(int x, int y) i promenljive z_global kreiraćemo fajl primer2.c sledećeg sadržaja:
Pokretanjem komande
kreira se objektni fajl primer2.o.
Analizom objektnih fajlova primer1.o i primer2.o možemo zaključiti da je moguće sve povezati u jednu celinu (svaka deklaracija ima svoju definiciju). Posao linkera je između ostalog da osigura da svaka stvar ima svoje mesto i da svako mesto ima svoju stvar.
Linker u stvari omogućava odvojeno prevođenje. Izvršni fajl može da bude sastavljen od većeg broja izvornih fajlova (prevedenih i sastavljenih u nezavisne objektne fajlove).
GCC kompajler povezuje fajlove pomocu alata ld. U procesu povezivanja objektnih fajlova neophodno je uključiti i potrebne sistemske fajlove. Spisak potrebnih fajlova se razlikuje od platforme do platforme, takođe i lokacija tih fajlova može da se razlikuje. Pozivanjem komande
se implicitno poziva alat ld i ispisuje koje se biblioteke koriste pri procesu povezivanja. Na primer na x64 Ubuntu 16.04 Linux-u pozivanjem komande
se kreira izvršni fajl primer koji povezuje primer1.o i primer2.o.
Pri procesu prevođenja na mašinski kod asembler uklanja sve labele iz koda. Objektni fajl mora da zadrži te informacije na drugoj lokaciji. Ta druga lokacija je tabela simbola koja sadrži listu imena i odgovarajuće adrese ka text ili data segmentima.
Objektni fajlovi uključuju reference ka kodovima i/ili podacima drugih objektnih fajlova, tj. ka različitim lokacijama, pa se sve mora ukomponovati u jednu celinu tokom procesa povezivanja. Na primer, objektni fajl koji sadrži funkciju main() može da uključuje pozive ka funkcijama funct() i printf(). Nakon povezivanja svih objektnih fajlova, linker koristi podatke iz sekcije relocation records da pronađe sve potrebne adrese.
U tipičnom sistemu biće pokrenut veći broj programa. Svaki program koristi veći broj funkcija od kojih su neke standardne C funkcije (printf(), malloc(), strcpy(), …), a neke su nestandardne ili korisnički definisane funkcije. Ako svaki program koristi standardne C biblioteke, to znači da bi svaki program trebalo da ima jedinstvenu kopiju te biblioteke u izvršnom fajlu. Takav pristup kao rezultat ima rasipanje resursa i pad efikasnosti i performansi. Zato što su C biblioteke uobičajne, bolje je da se programi referenciraju na jednu instancu biblioteke umesto da imaju svako svoju kopiju. Ovo može da se postigne tako što će neki objekti da se povezuju tokom povezivanja (statically linked), a neki tokom izvršenja (dynamic/deferred linking).
Statičko povezivanje je vrsta povezivanja u kome se program i određena biblioteka povezuju tako što izvršni fajl postaje unija tih objektnih fajlova. Njihovo povezivanje je fiksno i poznato pri procesu povezivanja (pre pokretanja programa). Takođe, nemoguće je promeniti ovu vezu osim ako ne pokrenemo ponovno povezivanje sa novom verzijom biblioteke. Program koji je statički povezan se povezuje sa arhivom objekata (biblioteka) koja obično ima ekstenziju .a. Primer ovakve kolekcije objekata je standardna C biblioteka, libc.a. Ovakav način povezivanja se obično koristi kada je potrebno povezati program sa tačno određenom verzijom biblioteke, a kada ne možemo biti sigurni da je ta verzija dostupna pri pokretanju programa. Loša strana ovakvog pristupa je ta da izvršni fajl je značajno veće veličine. Objektni fajlovi mogu da kreiraju statičku biblioteku korišćenjem alata ar.
Dinamičkim povezivanjem se program i određena biblioteka koju koristi ne kombinuju zajedno već povezuju pri pokretanju. Linker postavlja informacije u izvršni fajl koji govori loader-u u kom objektnom modulu i koji linker treba da koristi da bi se pronašle i povazale reference. Ovo znači da se povezivanje programa i deljenih objekta vrši ili pre nego što se program startuje (load-time dynamic linking) ili kada se referencira na na neki simbol iz biblioteke (run-time dynamic linking). U suštini, pri povezivanju linker samo proverava da li simboli deljenog objekta postoje u naznačenoj biblioteci i u izvršni fajl unosi informaciju na kojoj lokaciji se nalazi biblioteka. Na ovaj način se povezivanje odlaže sve do pokretanja programa ili kasnije.
Dinamičke biblioteke obično imaju prefiks lib i ekstenziju .so. Primer takvog objekta je deljena verzija standardne C biblioteke libc.so. Prednosti odloženog povezivanja nekih objekata/modula do trenutka kada su oni stvarno potrebni su sledeće:
GCC flag -fpic govori da se kod prevede u PIC (position-independent code). Takav kod radi bez obzira gde se u memoriji nalazi. Ne sadrži fiksne memorijske adrese, da bi takvu biblioteku mogli da koriste više procesa.
U Linux sistemima izvršni programi se učitavaju u ELF formatu. Pre izvršenja potrebno je učitati procese u RAM. Proces učitavanja se pokreće sistemskim pozivom execve() ili spawn(). Posao učitavanja obavlja loader koji je deo operativnog sistema. Loader, izveđu ostalog, radi sledeće: