Isang gabay ng baguhan sa paggamit ng linker. Linker

Ang layunin ng artikulong ito ay tulungan ang mga programmer ng C at C++ na maunawaan ang kakanyahan ng kung ano ang ginagawa ng isang linker. Ipinaliwanag ko ito sa maraming kasamahan sa nakalipas na ilang taon at sa wakas ay napagpasyahan kong oras na para ilagay ang materyal na ito sa papel para mas madaling ma-access ito (at kaya hindi ko na kailangang ipaliwanag muli). [Update March 2009: Nagdagdag ng higit pang impormasyon tungkol sa mga pagsasaalang-alang sa layout sa Windows, pati na rin ang higit pang detalye sa one-definition na panuntunan.

Ang isang karaniwang halimbawa kung bakit ang mga tao ay lumapit sa akin para sa tulong ay ang sumusunod na error sa layout:
g++ -o test1 test1a.o test1b.o test1a.o(.text+0x18): Sa function `main": : undefined reference to `findmax(int, int)" collect2: ld nagbalik ng 1 exit status
Kung ang iyong reaksyon ay "Malamang nakalimutan ko ang panlabas na "C", malamang na alam mo ang lahat ng ibinigay sa artikulong ito.

Mga Kahulugan: ano ang nasa isang C file?

Ang kabanatang ito ay isang mabilis na paalala ng iba't ibang bahagi ng isang C file. Kung ang lahat ay may katuturan sa iyo, malamang na maaari mong laktawan ang kabanatang ito at dumiretso sa.

Una kailangan mong maunawaan ang pagkakaiba sa pagitan ng isang deklarasyon at isang kahulugan. Kahulugan iniuugnay ang isang pangalan sa isang pagpapatupad, na maaaring alinman sa code o data:

  • Ang pagtukoy sa isang variable ay nagiging sanhi ng compiler na magreserba ng ilang lugar ng memorya, marahil ay nagbibigay ito ng ilang partikular na halaga.
  • Ang pagtukoy sa isang function ay nagiging sanhi ng compiler na makabuo ng code para sa function na iyon
Anunsyo nagsasabi sa compiler na ang isang kahulugan ng isang function o variable (na may partikular na pangalan) ay umiiral sa ibang lugar sa programa, marahil sa isa pang C file. (Tandaan na ang isang kahulugan ay isa ring deklarasyon - sa katunayan, ito ay isang deklarasyon kung saan ang "ibang lugar" ng programa ay kapareho ng kasalukuyang isa.)

Mayroong dalawang uri ng mga kahulugan para sa mga variable:

  • pandaigdigang mga variable, na umiiral sa buong ikot ng buhay ng programa ("static na alokasyon") at magagamit sa iba't ibang function;
  • mga lokal na variable, na umiiral lamang sa loob ng saklaw ng ilang gumaganang function ("lokal na pagkakalagay") at naa-access lamang sa loob ng mismong function na iyon.
Sa kasong ito, ang terminong "available" ay dapat na maunawaan bilang "maaaring ma-access ng pangalan na nauugnay sa variable sa oras ng kahulugan."

Mayroong ilang mga espesyal na kaso na maaaring hindi halata sa una:

  • Ang mga static na lokal na variable ay talagang pandaigdigan dahil umiiral ang mga ito sa buong buhay ng programa, kahit na makikita lamang ang mga ito sa loob ng isang function.
  • Ang mga static na global variable ay pandaigdigan din, na ang pagkakaiba lang ay available lang ang mga ito sa loob ng parehong file kung saan tinukoy ang mga ito.
Mahalagang tandaan na sa pamamagitan ng pagtukoy sa isang function bilang static, binabawasan mo lang ang bilang ng mga lugar kung saan maaari kang sumangguni sa isang ibinigay na function ayon sa pangalan.

Para sa mga global at lokal na variable, maaari nating makilala kung ang variable ay nasimulan o hindi, i.e. kung ang puwang na inilaan para sa isang variable sa memorya ay mapupunan ng isang tiyak na halaga.

Sa wakas, maaari tayong mag-imbak ng impormasyon sa memorya na dynamic na inilalaan gamit ang malloc o new . Sa kasong ito, hindi posible na ma-access ang inilalaan na memorya sa pamamagitan ng pangalan, kaya kinakailangan na gumamit ng mga pointer - pinangalanang mga variable na naglalaman ng address ng isang walang pangalan na lugar ng memorya. Ang lugar ng memorya na ito ay maaari ding palayain gamit ang libre o tanggalin. Sa kasong ito kami ay nakikitungo sa "dynamic na pagkakalagay".

Ibuod natin:

Marahil ang isang mas madaling paraan upang matuto ay ang tumingin lamang sa isang halimbawang programa.
/* Kahulugan ng isang hindi nasimulang global variable */ int x_global_uninit; /* Kahulugan ng isang inisyal na global variable */ int x_global_init = 1; /* Tukuyin ang isang uninitialized global variable na maaaring * ma-access sa pamamagitan ng pangalan lamang sa loob ng C file na ito */ static int y_global_uninit; /* Kahulugan ng isang inisyal na global variable na * maa-access sa pamamagitan ng pangalan lamang sa loob ng C file na ito */ static int y_global_init = 2; /* Deklarasyon ng isang global variable na tinukoy sa isang lugar * sa ibang lugar sa programa */ extern int z_global; /* Pagdedeklara ng function na tinukoy sa ibang lugar * sa programa (Maaari mong prepend "extern", ngunit ito * ay opsyonal) */ int fn_a(int x, int y); /* Depinisyon ng function. Gayunpaman, kapag minarkahan bilang static, maaari itong * tawagin sa pangalan lamang sa loob ng C file na iyon. */ static int fn_b(int x) ( return x+1; ) /* Depinisyon ng function. */ /* Ang isang parameter ng function ay itinuturing na isang lokal na variable. */ int fn_c(int x_local) ( /* Depinisyon ng isang hindi inisyal na lokal na variable */ int y_local_uninit; /* Depinisyon ng isang inisyal na lokal na variable */ int y_local_init = 3; /* Code na nag-a-access sa mga lokal at global na variable * at gumagana sa pamamagitan ng pangalan */ x_global_uninit = fn_a(x_local, x_global_init); y_local_uninit = fn_a(x_local, y_local_init); y_local_uninit += fn_b(z_global); return (x_global_uninit + y_local_uninit);

Ano ang ginagawa ng C compiler?

Ang trabaho ng C compiler ay i-convert ang text na (karaniwan) na nababasa ng tao sa isang bagay na naiintindihan ng isang computer. Sa output, gumagawa ang compiler object file. Sa mga platform ng UNIX, ang mga file na ito ay karaniwang may suffix na .o; sa Windows - suffix.obj. Ang mga nilalaman ng isang object file ay mahalagang dalawang bagay:

Ang code at data, sa kasong ito, ay magkakaroon ng mga pangalan na nauugnay sa kanila - ang mga pangalan ng mga function o variable kung saan nauugnay ang mga ito ayon sa kahulugan.

Ang Object code ay isang pagkakasunud-sunod ng (angkop na pagkakabuo) ng mga tagubilin ng makina na tumutugma sa mga tagubiling C na isinulat ng programmer: lahat ng mga if's at while's at kahit goto's. Ang mga spell na ito ay dapat na manipulahin ang impormasyon ng isang tiyak na uri, at ang impormasyon ay dapat na nasa isang lugar - kaya kailangan namin ng mga variable. Ang code ay maaari ding sumangguni sa iba pang code (lalo na sa iba pang C function sa programa).

Saanman ang code ay tumutukoy sa isang variable o function, pinapayagan lamang ito ng compiler kung nakita na nito ang variable o function na iyon dati. Ang deklarasyon ay isang pangako na mayroong isang kahulugan sa ibang lugar sa programa.

Ang trabaho ng linker ay i-verify ang mga pangakong ito. Gayunpaman, ano ang ginagawa ng compiler sa lahat ng mga pangakong ito kapag bumubuo ito ng object file?

Sa pangkalahatan, ang compiler ay nag-iiwan ng mga walang laman na puwang. Ang walang laman na espasyo (link) ay may pangalan, ngunit ang halaga na nauugnay sa pangalang ito ay hindi pa alam.

Dahil dito, maaari naming ilarawan ang object file na naaayon sa , tulad ng sumusunod:

Pag-parse ng object file

Hanggang ngayon ay isinasaalang-alang namin ang lahat sa isang mataas na antas. Gayunpaman, ito ay kapaki-pakinabang upang makita kung paano ito gumagana sa pagsasanay. Ang pangunahing tool para sa amin ay ang koponan nm, na nagbibigay ng impormasyon tungkol sa mga simbolo ng object file sa UNIX platform. Para sa utos ng Windows dumpbin na may opsyon na /symbols ay isang tinatayang katumbas. Mayroon ding mga tool sa GNU binutils na kinabibilangan ng nm.exe.

Tingnan natin kung ano ang ginagawa ng nm para sa isang object file na nakuha mula sa :
Mga simbolo mula sa c_parts.o: Pangalan Halaga Uri ng Klase Sukat ng Seksyon ng Linya fn_a | | U | NOTYPE| | |*UND* z_global | | U | NOTYPE| | |*UND* fn_b |00000000| t | FUNC|00000009| |.text x_global_init |00000000| D | OBJECT|00000004| |.data y_global_uninit |00000000| b | OBJECT|00000004| |.bss x_global_uninit |00000004| C | OBJECT|00000004| |*COM* y_global_init |00000004| d | OBJECT|00000004| |.data fn_c |00000009| T | FUNC|00000055| |.teksto
Ang resulta ay maaaring bahagyang naiiba sa iba't ibang mga platform (tingnan ang man page para sa mga detalye), ngunit ang pangunahing impormasyon ay ang klase ng bawat karakter at ang laki nito (kung mayroon). Ang klase ay maaaring magkaroon ng iba't ibang kahulugan:

  • Klase U nagsasaad ng mga hindi natukoy na link, ang parehong "mga bakanteng espasyo" na binanggit sa itaas. Mayroong dalawang bagay para sa klase na ito: fn_a at z_global. (Ang ilang mga bersyon ng nm ay maaaring mag-output seksyon, na magiging *UND* o UNDEF sa kasong ito.)
  • Mga klase t At T ipahiwatig ang isang code na tinukoy; pagkakaiba sa pagitan ng t At T ay kung ang function ay lokal ( t) sa file o hindi ( T), ibig sabihin. kung ang function ay idineklara bilang static . Muli sa ilang system ay maaaring ipakita ang isang seksyon, hal. .text.
  • Mga klase d At D naglalaman ng mga inisyal na global variable. Sa kasong ito, ang mga static na variable ay nabibilang sa klase d. Kung ang impormasyon ng seksyon ay naroroon, ito ay magiging .data.
  • Para sa hindi nasimulang mga global na variable, nakukuha namin b, kung sila ay static at B o C kung hindi. Ang seksyon sa kasong ito ay malamang na .bss o *COM*.
Maaari ka ring makakita ng mga simbolo na hindi bahagi ng C source code. Hindi namin itutuon ang aming pansin dito, dahil kadalasan ito ay bahagi ng panloob na mekanismo ng compiler, upang ang iyong programa ay maiugnay pa rin sa ibang pagkakataon.

Ano ang Ginagawa ng Linker: Bahagi 1

Sinabi namin kanina na ang pagdedeklara ng function o variable ay isang pangako sa compiler na mayroong kahulugan ng function o variable na iyon sa ibang lugar sa program, at ang trabaho ng linker ay tuparin ang pangakong iyon. Sa pagtingin sa , maaari naming ilarawan ang prosesong ito bilang "pagpuno sa mga patlang."

Ilarawan natin ito sa isang halimbawa, tumitingin sa isa pang C file bilang karagdagan sa isa na .
/* Nagsimulang global variable */ int z_global = 11; /* Pangalawang global variable na pinangalanang y_global_init, ngunit pareho silang static */ static int y_global_init = 2; /* Deklarasyon ng isa pang global variable */ extern int x_global_init; int fn_a(int x, int y) ( return(x+y); ) int main(int argc, char *argv) ( const char *message = "Hello, world"; return fn_a(11,12); )

Mula sa parehong mga diagram, makikita natin na ang lahat ng mga punto ay maaaring konektado (kung hindi, ang linker ay magtapon ng isang mensahe ng error). Bawat bagay ay may kani-kaniyang lugar, at bawat lugar ay may kanya-kanyang bagay. Ang linker ay maaari ring punan ang anumang walang laman na mga puwang tulad ng ipinapakita dito (sa UNIX system, ang proseso ng pag-link ay karaniwang tinatawag na may command ld).

Para sa C ang sitwasyon ay hindi gaanong halata. Dapat mayroong eksaktong isang kahulugan para sa anumang function at inisyal na global variable, ngunit ang kahulugan ng isang uninitialized variable ay maaaring ituring bilang paunang pagpapasiya. Sa gayon, pinapayagan ng wikang C (o hindi bababa sa hindi pinipigilan) ang iba't ibang mga source file na naglalaman ng mga predefinition ng parehong bagay.

Gayunpaman, ang mga linker ay dapat ding makayanan ang mga wika maliban sa C at C++, kung saan ang panuntunang may isang kahulugan ay hindi kinakailangang nalalapat. Halimbawa, normal para sa Fortran na magkaroon ng kopya ng bawat pandaigdigang variable sa bawat file na nagre-refer dito. Kailangang alisin ng linker ang mga duplicate sa pamamagitan ng pagpili ng isang kopya (ang pinakamalaking kinatawan, kung magkaiba ang laki ng mga ito) at itapon ang lahat ng iba pa. Ang modelong ito kung minsan ay tinatawag na "karaniwang modelo" ng layout dahil sa COMMON na keyword ng wikang Fortran.

Bilang resulta, karaniwan na para sa mga linker ng UNIX na hindi magreklamo tungkol sa mga duplicate na simbolo, kahit man lang kung sila ay mga duplicate na simbolo ng hindi nasimulang mga global na variable (ang modelong ito sa pagli-link ay tinatawag minsan na "loose-coupled model" [ tinatayang pagsasalin Ito ang aking libreng pagsasalin ng nakakarelaks na modelo ng ref/def. Malugod na tinatanggap ang mas mahusay na mga mungkahi]). Kung ito ay nag-aalala sa iyo (at marahil ay dapat), kumonsulta sa dokumentasyon ng iyong linker upang makahanap ng isang --work-right na opsyon na nagpapaamo sa gawi nito. Halimbawa, sa GNU toolchain, pinipilit ng -fno-common compiler na opsyon ang isang uninitialized variable na ilagay sa BBS segment sa halip na bumuo ng COMMON blocks.

Ano ang ginagawa ng operating system?

Ngayon na ang linker ay gumawa ng isang maipapatupad na file, na nagbibigay sa bawat simbolo ng reference ng naaangkop na kahulugan, maaari mong i-pause sandali upang maunawaan kung ano ang ginagawa ng operating system kapag pinatakbo mo ang programa.

Ang pagpapatakbo ng isang programa, siyempre, ay nangangailangan ng pagpapatupad ng machine code, i.e. Malinaw na kailangang ilipat ng OS ang machine code ng executable file mula sa hard drive patungo sa operating memory, kung saan maaaring kunin ito ng CPU. Ang mga bahaging ito ay tinatawag na segment ng code o segment ng teksto.

Ang code na walang data mismo ay walang silbi. Samakatuwid, ang lahat ng mga global na variable ay nangangailangan din ng espasyo sa memorya ng computer. Gayunpaman, mayroong pagkakaiba sa pagitan ng nasimulan at hindi nasimulang mga global na variable. Ang mga inisyal na variable ay may ilang partikular na panimulang halaga, na dapat ding nakaimbak sa object at executable na mga file. Kapag nagsimula ang programa, kinokopya ng OS ang mga halagang ito sa virtual space ng programa, sa segment ng data.

Para sa mga hindi nasimulang variable, maaaring ipagpalagay ng OS na lahat sila ay may 0 bilang kanilang paunang halaga, i.e. hindi na kailangang kopyahin ang anumang mga halaga. Ang piraso ng memorya na pinasimulan ng mga zero ay kilala bilang bss segment.

Nangangahulugan ito na ang espasyo para sa mga pandaigdigang variable ay maaaring ilaan sa isang maipapatupad na file na nakaimbak sa disk; Ang mga inisyal na variable ay dapat na panatilihin ang kanilang mga paunang halaga, ngunit ang mga hindi pa nasimulan ay kailangan lamang na mapanatili ang kanilang laki.

Tulad ng napansin mo, sa ngayon sa lahat ng mga talakayan tungkol sa mga object file at ang linker ay napag-usapan lang namin ang tungkol sa mga pandaigdigang variable; Kasabay nito, hindi namin binanggit ang mga lokal na variable at dynamic na inookupahan ng memorya.

Ang data na ito ay hindi nangangailangan ng anumang interbensyon ng linker dahil ang buhay nito ay nagsisimula at nagtatapos sa panahon ng pagpapatupad ng programa—matagal nang matapos ang linker na magawa ang trabaho nito. Gayunpaman, para sa kapakanan ng pagkakumpleto, maikli naming ituturo na:

  • Ang mga lokal na variable ay matatagpuan sa isang lugar ng memorya na tinatawag salansan, na lumalaki at kumukontra habang tinatawag at isinasagawa ang iba't ibang function.
  • Ang dynamic na inilaan na memorya ay kinuha mula sa isang lugar ng memorya na kilala bilang isang bungkos, at kinokontrol ng malloc function ang pag-access sa libreng espasyo sa lugar na ito.
Upang makumpleto ang larawan, sulit na idagdag kung ano ang hitsura ng memory space ng proseso ng pagpapatakbo. Dahil dynamic na maaaring baguhin ng heap at stack ang kanilang mga laki, karaniwan na ang stack ay lumalaki sa isang direksyon at ang heap ay lumalaki sa kabilang direksyon. Kaya, ang programa ay magtapon lamang ng isang libreng error sa memorya kung ang stack at heap ay magtagpo sa isang lugar sa gitna (kung saan ang memory space ng programa ay talagang puno).

Ano ang ginagawa ng linker? bahagi 2

Ngayong napag-usapan na natin kung ano ang ginagawa ng linker, maaari na tayong sumisid sa mas kumplikadong mga bahagi - halos sa magkakasunod na pagkakasunud-sunod kung paano sila idinagdag sa linker.

Ang pangunahing obserbasyon na nakakaapekto sa pag-andar ng linker ay ang mga sumusunod: kung ang isang bilang ng iba't ibang mga programa ay gumagawa ng humigit-kumulang sa parehong mga bagay (output sa screen, pagbabasa ng mga file mula sa hard drive, atbp.), pagkatapos ay malinaw na makatuwiran na ihiwalay ito code sa isang partikular na lugar at ibigay ito sa iba pang mga programa upang magamit ito.

Ang isang posibleng solusyon ay ang paggamit ng parehong object file, ngunit magiging mas maginhawang panatilihin ang buong koleksyon ng... object file sa isang madaling ma-access na lokasyon: aklatan.

Bukod sa teknikal: Ang kabanatang ito ay ganap na nag-aalis ng mahalagang katangian ng linker: pag-redirect(relokasyon). Ang iba't ibang mga programa ay may iba't ibang laki, i.e. kung ang isang shared library ay nakamapa sa address space ng iba't ibang mga program, magkakaroon ito ng magkakaibang mga address. Nangangahulugan ito na ang lahat ng mga function at variable sa library ay nasa iba't ibang lugar. Ngayon, kung ang lahat ng mga sanggunian sa address ay kamag-anak ("halaga +1020 bytes mula rito") sa halip na ganap ("halaga sa 0x102218BF"), hindi ito isang problema, ngunit hindi ito palaging ang kaso. Sa ganitong mga kaso, ang lahat ng ganap na address ay dapat idagdag na may angkop na offset - ito ay relokasyon. Hindi na ako babalik sa paksang ito muli, ngunit idaragdag ko na dahil ito ay halos palaging nakatago mula sa C/C++ programmer, napakabihirang na ang mga problema sa layout ay sanhi ng mga paghihirap sa pag-redirect.

Mga static na aklatan

Ang pinakasimpleng pagpapatupad ng library ay static aklatan. Nabanggit sa nakaraang kabanata na maaari mong ibahagi ang code sa pamamagitan lamang ng muling paggamit ng mga object file; ito ang kakanyahan ng mga static na aklatan.

Sa mga sistema ng UNIX, ang utos na bumuo ng isang static na library ay karaniwang ar, at ang resultang library file ay may extension *.a. Gayundin, ang mga file na ito ay karaniwang may prefix na "lib" sa kanilang pangalan at ipinapasa ang mga ito sa linker na may opsyong "-l" na sinusundan ng pangalan ng library na walang prefix at extension (ibig sabihin, kukunin ng "-lfred" ang file na " libfred.a").
(Noong nakaraan, kailangan din ang isang program na tinatawag na ranlib para sa mga static na aklatan upang makabuo ng isang listahan ng mga simbolo sa harap ng library. Sa mga araw na ito, ang mga ar tool mismo ang gumagawa nito.)

Sa Windows, ang mga static na aklatan ay may extension na .LIB at binuo ng mga tool ng LIB, ngunit ang katotohanang ito ay maaaring mapanlinlang, dahil ang parehong extension ay ginagamit para sa "import library", na naglalaman lamang ng isang listahan ng kung ano ang nasa DLL - tingnan mo

Habang umuulit ang linker sa pamamagitan ng isang koleksyon ng mga object file upang pagsamahin ang mga ito, nagpapanatili ito ng isang listahan ng mga simbolo na hindi pa maipapatupad. Kapag naproseso na ang lahat ng tahasang tinukoy na object file, mayroon na ngayong bagong lugar ang linker para maghanap ng mga simbolo na nananatili sa listahan - sa library. Kung ang isang hindi ipinatupad na simbolo ay tinukoy sa isa sa mga bagay sa library, pagkatapos ay idinagdag ang bagay, tulad ng kung ito ay idinagdag sa listahan ng mga object file ng user, at magpapatuloy ang pag-link.

Pansinin ang granularity ng kung ano ang idinagdag mula sa library: kung kailangan ng isang kahulugan ng ilang simbolo, kung gayon ang buong bagay, na naglalaman ng kahulugan ng simbolo, ay isasama. Nangangahulugan ito na ang prosesong ito ay maaaring maging isang hakbang pasulong o isang hakbang pabalik - ang isang bagong idinagdag na bagay ay maaaring malutas ang isang hindi natukoy na sanggunian o magpakilala ng isang buong koleksyon ng mga bagong hindi nalutas na sanggunian.

Ang isa pang mahalagang detalye ay utos mga kaganapan; Ang mga aklatan ay ginagamit lamang kapag ang normal na pag-link ay kumpleto at ang mga ito ay naproseso ok mula kaliwa hanggang kanan. Nangangahulugan ito na kung ang huling bagay na nakuha mula sa library ay nangangailangan ng isang simbolo mula sa library na mas maaga sa linya ng command ng link, hindi ito awtomatikong mahahanap ng linker.

Magbigay tayo ng isang halimbawa upang linawin ang sitwasyon; Sabihin nating mayroon tayong mga sumusunod na object file at isang link command line na naglalaman ng a.o, b.o, -lx at -ly .


Kapag naproseso na ng linker ang a.o at b.o , malulutas ang mga reference sa b2 at a3, habang hindi pa rin malulutas ang x12 at y22. Sa puntong ito, sinusuri ng linker ang unang library, libx.a, para sa mga nawawalang simbolo at nalaman na maaari itong isama ang x1.o upang mabayaran ang reference sa x12; gayunpaman, sa paggawa nito, ang x23 at y12 ay idinaragdag sa listahan ng mga hindi natukoy na sanggunian (ang listahan ngayon ay mukhang y22, x23, y12).

Ang linker ay nakikitungo pa rin sa libx.a , kaya ang reference sa x23 ay madaling mabayaran sa pamamagitan ng pagsasama ng x2.o mula sa libx.a . Gayunpaman, idinaragdag nito ang y11 sa hindi natukoy na listahan (na naging y22, y12, y11). Wala sa mga link na ito ang maaaring lutasin gamit ang libx.a , kaya ang linker ay ipinapalagay na liby.a .

Ang parehong bagay ay nangyayari dito at ang linker ay kinabibilangan ng y1.o at y2.o . Ang unang bagay na idinagdag ay isang reference sa y21 , ngunit dahil isasama pa rin ang y2.o, ang reference na ito ay naresolba nang simple. Ang resulta ng prosesong ito ay ang lahat ng hindi natukoy na mga sanggunian ay nalutas at ang ilan (ngunit hindi lahat) ng mga bagay sa aklatan ay kasama sa panghuling maipapatupad.

Tandaan na medyo nagbabago ang sitwasyon kung sasabihin nating may link din si b.o sa y32 . Kung ito ang kaso, ang pag-link sa libx.a ay magaganap sa parehong paraan, ngunit ang pagproseso ng liby.a ay kasangkot kasama ang y3.o . Sa pamamagitan ng pagsasama ng bagay na ito, idaragdag namin ang x31 sa listahan ng mga hindi nalutas na simbolo at ang sanggunian na ito ay mananatiling hindi nalutas - sa yugtong ito natapos na ng linker ang pagproseso ng libx.a at samakatuwid ay hindi na mahahanap ang kahulugan ng simbolong ito (sa x3.o) .

(Nga pala, ang halimbawang ito ay may pabilog na dependency sa pagitan ng libx.a at liby.a; ito ay karaniwang isang masamang bagay)

Mga dynamic na shared library

Para sa mga sikat na aklatan gaya ng C standard library (karaniwan ay libc), ang pagiging isang static na library ay may natatanging disadvantage - bawat executable program ay magkakaroon ng kopya ng parehong code. Sa katunayan, kung ang bawat maipapatupad na file ay may kopya ng printf , fopen at mga katulad nito, kung gayon ang isang hindi makatwirang malaking halaga ng espasyo sa disk ay mauubos.

Ang isang hindi gaanong halatang kawalan ay na sa isang statically linked na programa ang code ay naayos magpakailanman. Kung may makakita at nag-aayos ng bug sa printf , ang bawat program ay kailangang muling i-link upang makuha ang tamang code.

Upang maalis ang mga ito at iba pang mga problema, ipinakilala ang mga dynamic na nakabahaging aklatan (karaniwan ay mayroon silang extension na .so o .dll sa Windows at .dylib sa Mac OS X). Para sa ganitong uri ng library, hindi kinakailangang ikinonekta ng linker ang lahat ng tuldok. Sa halip, nag-iisyu ang linker ng kupon na may uri na "IOU" (Utang ko sa iyo) at inaantala ang pag-cash sa kupon na ito hanggang sa tumakbo ang programa.

Ang pinagbabatayan ng lahat ng ito ay kung natukoy ng linker na ang kahulugan ng isang partikular na simbolo ay nasa isang shared library, hindi nito kasama ang kahulugang iyon sa panghuling executable. Sa halip, isusulat ng linker ang pangalan ng simbolo at ang aklatan kung saan inaasahang manggagaling ang simbolo.

Kapag ang isang programa ay tinawag para sa pagpapatupad, tinitiyak ng OS na ang natitirang bahagi ng proseso ng pag-link ay nakumpleto sa oras bago magsimulang tumakbo ang programa. Bago tawagan ang pangunahing function, ang isang maliit na bersyon ng linker (madalas na tinatawag na ld.so) ay lumalakad sa listahan ng pangako at nagsasagawa ng panghuling pagkilos ng pagli-link sa lugar - paglalagay sa code ng library at pagkonekta sa mga tuldok.

Nangangahulugan ito na walang executable file na naglalaman ng kopya ng printf code. Kung may available na bagong bersyon ng printf, magagamit mo ito sa pamamagitan lamang ng pagpapalit ng libc.so - sa susunod na patakbuhin mo ang program, tatawagin ang bagong printf.

May isa pang malaking pagkakaiba sa pagitan ng kung paano gumagana ang mga dynamic na library kumpara sa mga static at ito ay dumating sa anyo ng linkage granularity. Kung ang isang partikular na simbolo ay kinuha mula sa isang partikular na dynamic na library (sabihin ang printf mula sa libc.so), ang buong nilalaman ng library ay inilalagay sa address space ng program. Ito ang pangunahing pagkakaiba mula sa mga static na aklatan, kung saan tanging mga partikular na bagay na nauugnay sa isang hindi natukoy na simbolo ang idinaragdag.

Upang ilagay ito sa ibang paraan, ang mga nakabahaging aklatan mismo ay nakuha bilang resulta ng gawain ng linker (at hindi bilang pagbuo ng isang malaking bunton ng mga bagay, tulad ng ginagawa ng ar), na naglalaman ng mga sanggunian sa pagitan ng mga bagay sa library mismo. Muli, ang nm ay isang kapaki-pakinabang na tool upang ilarawan kung ano ang nangyayari: gagawa ito ng maramihang mga output para sa bawat indibidwal na object file kapag tumatakbo sa isang static na bersyon ng library, ngunit para sa isang nakabahaging bersyon ng library, ang liby.so ay mayroon lamang isang hindi natukoy na x31 simbolo. Gayundin, sa halimbawa na may pagkakasunud-sunod ng pagsasama ng mga aklatan sa dulo, hindi rin magkakaroon ng mga problema: ang pagdaragdag ng isang link sa y32 sa b.c ay hindi magkakaroon ng anumang mga pagbabago, dahil ang lahat ng mga nilalaman ng y3.o at x3.o ay mayroon na ginamit.

Kaya nga pala, ang isa pang kapaki-pakinabang na tool ay ldd; sa Unix platform, ipinapakita nito ang lahat ng shared library kung saan nakasalalay ang executable binary (o iba pang shared library), kasama ang indikasyon kung saan makikita ang mga library na iyon. Upang matagumpay na mailunsad ang programa, kailangang mahanap ng loader ang lahat ng mga aklatang ito kasama ang lahat ng kanilang mga dependency. (Karaniwan, ang loader ay naghahanap ng mga aklatan sa listahan ng mga direktoryo na tinukoy sa LD_LIBRARY_PATH na environment variable.)
/usr/bin:ldd xeyes linux-gate.so.1 => (0xb7efa000) libXext.so.6 => /usr/lib/libXext.so.6 (0xb7edb000) libXmu.so.6 => /usr/lib /libXmu.so.6 (0xb7ec6000) libXt.so.6 => /usr/lib/libXt.so.6 (0xb7e77000) libX11.so.6 => /usr/lib/libX11.so.6 (0xb7d93000) libSM .so.6 => /usr/lib/libSM.so.6 (0xb7d8b000) libICE.so.6 => /usr/lib/libICE.so.6 (0xb7d74000) libm.so.6 => /lib/libm .so.6 (0xb7d4e000) libc.so.6 => /lib/libc.so.6 (0xb7c05000) libXau.so.6 => /usr/lib/libXau.so.6 (0xb7c01000) libxcb-xlib.so .0 => /usr/lib/libxcb-xlib.so.0 (0xb7bff000) libxcb.so.1 => /usr/lib/libxcb.so.1 (0xb7be8000) libdl.so.2 => /lib/libdl .so.2 (0xb7be4000) /lib/ld-linux.so.2 (0xb7efb000) libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0xb7bdf000)
Ang dahilan para sa mas malaking granularity ay ang mga modernong operating system ay sapat na matalino upang payagan kang gumawa ng higit pa kaysa sa pag-save lamang ng mga duplicate na item sa disk, isang bagay na dinaranas ng mga static na aklatan. Ang iba't ibang proseso ng pagpapatupad na gumagamit ng parehong nakabahaging library ay maaari ding magbahagi ng segment ng code (ngunit hindi segment ng data o segment ng bss - halimbawa, maaaring nasa magkaibang lugar ang dalawang magkaibang proseso kapag gumagamit, halimbawa, strtok). Upang makamit ito, ang buong aklatan ay dapat na matugunan sa isang pagkakataon upang ang lahat ng mga panloob na link ay nakahanay sa isang natatanging paraan. Sa katunayan, kung kukunin ng isang proseso ang a.o at c.o , at isa pang proseso ang b.o at c.o , hindi magagamit ng OS ang anumang mga tugma.

Windows DLL

Kahit na ang mga pangkalahatang prinsipyo ng mga shared library ay halos pareho sa parehong Unix at Windows platform, mayroon pa ring ilang mga detalye na maaaring makuha ng mga nagsisimula.

Mga na-export na simbolo

Ang pinakamalaking pagkakaiba ay na sa Windows library, ang mga simbolo ay hindi na-export awtomatiko. Sa Unix, ang lahat ng simbolo ng lahat ng object file na na-link sa isang shared library ay makikita ng user ng library na iyon. Sa Windows, dapat na tahasang gawing nakikita ng programmer ang ilang mga character, i.e. i-export sila.

Mayroong tatlong mga paraan upang i-export ang isang simbolo at isang Windows DLL (at lahat ng tatlong mga pamamaraan na ito ay maaaring ihalo sa parehong library).

  • Sa source code, ideklara ang simbolo bilang __declspec(dllexport) , tulad nito:
    __declspec(dllexport) int my_exported_function(int x, double y)
  • Kapag isinasagawa ang linker command, gamitin ang LINK.EXE export na opsyon: symbol_to_export
    LINK.EXE /dll /export:my_exported_function
  • Ipakain ang module definition file (DEF) sa linker (gamit ang /DEF na opsyon: def_file), sa pamamagitan ng pagsasama ng seksyong EXPORT sa file na ito, na naglalaman ng mga simbolo na ie-export.
    EXPORTS my_exported_function my_other_exported_function
Kapag ang C++ ay kasangkot sa gulo na ito, ang una sa mga opsyon na ito ay nagiging pinakasimpleng, dahil sa kasong ito ang compiler ay may pananagutan sa pag-aalaga ng

.LIB at iba pang mga file na nauugnay sa library

Dumating kami sa pangalawang kahirapan sa mga aklatan ng Windows: ang impormasyon tungkol sa mga na-export na simbolo na dapat i-link ng linker sa iba pang mga simbolo ay hindi nakapaloob sa mismong DLL. Sa halip, ang impormasyong ito ay nasa kaukulang .LIB file.

Ang LIB file na nauugnay sa DLL ay naglalarawan kung aling (na-export) na mga simbolo ang nasa DLL kasama ang kanilang lokasyon. Ang anumang binary na gumagamit ng DLL ay dapat ma-access ang .LIB file upang mai-link nang tama ang mga simbolo.

Upang gawing mas nakakalito ang mga bagay, ginagamit din ang extension na .LIB para sa mga static na library.

Sa katunayan, mayroong ilang iba't ibang mga file na maaaring nauugnay sa ilang paraan sa mga aklatan ng Windows. Kasama ang .LIB file, gayundin ang (opsyonal) .DEF file, makikita mo ang lahat ng sumusunod na file na nauugnay sa iyong Windows library.

Malaking pagkakaiba ito sa Unix, kung saan halos lahat ng impormasyong nakapaloob sa lahat ng mga karagdagang file na ito ay idinagdag lamang sa mismong aklatan.

Mga na-import na simbolo

Bilang karagdagan sa pag-aatas sa mga DLL na tahasang ideklara ang , pinapayagan din ng Windows ang mga binary na gumagamit ng library code na tahasang ideklara ang mga simbolo na ii-import. Hindi ito sapilitan, ngunit nagbibigay ng ilang bilis ng pag-optimize dahil sa mga makasaysayang katangian ng 16-bit na mga bintana.

Maaari naming subaybayan ang mga listahang ito, muli gamit ang nm . Isaalang-alang ang sumusunod na C++ file:
klase Fred ( pribado: int x; int y; pampubliko: Fred() : x(1), y(2) () Fred(int z): x(z), y(3) () ); Fred theFred; Fred theOtherFred(55);
Para sa code na ito ( Hindi decorated) ang nm output ay ganito:
Mga simbolo mula sa global_obj.o: Pangalan Halaga Uri ng Klase Sukat Seksyon ng Linya __gxx_personality_v0| | U | NOTYPE| | |*UND* __static_initialization_and_destruction_0(int, int) |00000000| t | FUNC|00000039| |.text Fred::Fred(int) |00000000| W | FUNC|00000017| |.text._ZN4FredC1Ei Fred::Fred() |00000000| W | FUNC|00000018| |.text._ZN4FredC1Ev theFred |00000000| B | OBJECT|00000008| |.bss theOtherFred |00000008| B | OBJECT|00000008| |.bss global constructors na naka-key sa theFred |0000003a| t | FUNC|0000001a| |.teksto
Gaya ng dati, marami tayong makikitang iba't ibang bagay dito, ngunit isa sa mga pinakakawili-wili sa atin ay ang mga post sa klase W(na nangangahulugang isang “mahina” na simbolo) pati na rin ang mga entry na may pangalan ng seksyon tulad ng ".gnu.linkonce.t. bagay". Ito ay mga marker para sa mga constructor ng mga global na bagay at nakikita namin na ang kaukulang field na "Pangalan" ay nagpapakita kung ano talaga ang maaari naming asahan doon - bawat isa sa dalawang constructor ay kasangkot.

Mga template

Noong nakaraan, nagbigay kami ng tatlong magkakaibang pagpapatupad ng max function, na ang bawat isa ay kumuha ng iba't ibang uri ng mga argumento. Gayunpaman, nakikita namin na ang code ng function ng katawan ay magkapareho sa lahat ng tatlong mga kaso. At alam namin na ang pagdoble sa parehong code ay masamang kasanayan sa programming.

Ang C++ ay nagpapakilala ng mga konsepto template(mga template), na nagbibigay-daan sa iyong gamitin ang code sa ibaba para sa lahat ng kaso nang sabay-sabay. Maaari kaming lumikha ng isang header file na max_template.h na may isang kopya lamang ng max function code:
template T max(T x, T y) ( kung (x>y) ibalik ang x; kung hindi ibalik ang y; )
at isama ang file na ito sa source file para subukan ang template function:
#include "max_template.h" int main() ( int a=1; int b=2; int c; c = max(a,b); // Awtomatikong tinutukoy ng compiler kung ano ang eksaktong max na kailangan (int,int) dobleng x = 1.1; float y = 2.2; dobleng z; z = max (x,y); // Hindi matukoy ng compiler, kaya kailangan namin ng max (doble,doble) return 0; )
Ang code na ito, na nakasulat sa C++, ay gumagamit ng max (int,int) at max (doble, doble). Gayunpaman, ang ilang iba pang code ay maaaring gumamit ng iba pang mga pagkakataon ng pattern na ito. Well, sabihin nating max (float,float) o kahit na max (MyFloatingPointClass,MyFloatingPointClass) .

Ang bawat isa sa magkakaibang pagkakataong ito ay gumagawa ng iba't ibang machine code. Kaya, sa oras na ang programa ay sa wakas ay naka-link, ang compiler at linker ay dapat tiyakin na ang code para sa bawat ginamit na halimbawa ng template ay kasama sa programa (at na walang hindi nagamit na halimbawa ng template ay kasama, upang hindi lumaki ang laki ng programa. ).

Paano ito ginagawa? Karaniwang mayroong dalawang kurso ng pagkilos: alinman sa pagnipis ng mga duplicate na pagkakataon o pagpapaliban ng instantiation hanggang sa yugto ng link (karaniwan kong tinutukoy ang mga diskarteng ito bilang matalinong paraan at paraan ng Araw).

Ang paraan ng pagnipis ng mga umuulit na pagkakataon ay nagpapahiwatig na ang bawat object file ay naglalaman ng code ng lahat ng nakatagpo na mga template. Halimbawa, para sa file sa itaas, ang mga nilalaman ng object file ay ganito ang hitsura:
Mga simbolo mula sa max_template.o: Pangalan Halaga Uri ng Klase Sukat Seksyon ng Linya __gxx_personality_v0 | | U | NOTYPE| | |*UND* double max (doble, doble) |00000000| W | FUNC|00000041| |.text _Z3maxIdET_S0_S0_ int max (int, int) |00000000| W | FUNC|00000021| |.text._Z3maxIiET_S0_S0_ pangunahing |00000000| T | FUNC|00000073| |.teksto
At nakikita namin ang presensya ng parehong mga pagkakataon na max (int,int) at max (doble, doble).

Ang parehong mga kahulugan ay minarkahan bilang mahinang mga karakter, at nangangahulugan ito na ang linker, kapag lumilikha ng panghuling maipapatupad na file, ay maaaring itapon ang lahat ng mga duplicate na instance ng parehong template at mag-iwan lamang ng isa (at kung sa tingin nito ay kinakailangan, maaari nitong suriin kung ang lahat ng mga duplicate na instance ng template ay talagang mapa sa ang parehong code). Ang pinakamalaking kawalan ng diskarteng ito ay ang pagtaas sa laki ng bawat indibidwal na object file.

Ang isa pang diskarte (na ginagamit sa Solaris C++) ay huwag isama ang mga kahulugan ng template sa mga object file, ngunit markahan ang mga ito bilang mga hindi natukoy na simbolo. Pagdating sa yugto ng pag-link, maaaring kolektahin ng linker ang lahat ng hindi natukoy na mga simbolo na talagang nabibilang sa mga halimbawa ng template, at pagkatapos ay bumuo ng machine code para sa bawat isa sa kanila.

Tiyak na binabawasan nito ang laki ng bawat object file, ngunit ang downside sa diskarteng ito ay dapat na subaybayan ng linker kung saan matatagpuan ang source code at dapat na kayang patakbuhin ang C++ compiler sa oras ng link (na maaaring makapagpabagal sa buong proseso. )

Mga library na dynamic na na-load

Ang huling feature na tatalakayin natin dito ay ang dynamic na paglo-load ng mga shared library. Sa nakita namin kung paano ipinagpaliban ng paggamit ng mga shared library ang huling pag-link hanggang sa aktwal na tumakbo ang programa. Sa mga modernong OS, posible pa rin ito sa mga susunod na yugto.

Naisasagawa ito ng isang pares ng system call, dlopen at dlsym (ang tinatayang katumbas sa Windows ay tinatawag na LoadLibrary at GetProcAddress, ayon sa pagkakabanggit). Kinukuha ng una ang pangalan ng nakabahaging library at nilo-load ito sa address space ng tumatakbong proseso. Siyempre, ang library na ito ay maaaring mayroon ding mga hindi nalutas na simbolo, kaya ang pagtawag sa dlopen ay maaaring mangailangan ng pag-load ng iba pang mga shared library.

Nag-aalok ang Dlopen ng pagpipilian ng alinman sa pag-clear sa lahat ng hindi nalutas sa sandaling ma-load ang library (RTLD_NOW) o paglutas ng mga simbolo kung kinakailangan (RTLD_LAZY). Ang unang paraan ay nangangahulugan na ang pagtawag sa dlopen ay maaaring tumagal ng ilang oras, ngunit ang pangalawang paraan ay nagpapakilala ng isang tiyak na panganib na sa panahon ng pagpapatupad ng programa ay matatagpuan ang isang hindi natukoy na sanggunian na hindi malulutas, kung saan ang programa ay magwawakas.

Siyempre, hindi maaaring magkaroon ng pangalan ang mga simbolo mula sa isang dynamic na na-load na library. Gayunpaman, madali itong malulutas, tulad ng paglutas ng iba pang mga problema sa programming, sa pamamagitan ng pagdaragdag ng karagdagang layer ng mga workaround. Sa kasong ito, ginagamit ang isang pointer sa espasyo ng character. Ang tawag sa dlsym ay tumatagal ng literal na parameter na tumutukoy sa pangalan ng simbolo na mahahanap at nagbabalik ng pointer sa lokasyon nito (o NULL kung hindi natagpuan ang simbolo).

Interoperability sa C++

Ang dynamic na proseso ng paglo-load ay medyo diretso, ngunit paano ito nakikipag-ugnayan sa iba't ibang mga tampok ng C++ na nakakaapekto sa pangkalahatang pag-uugali ng linker?

Ang unang obserbasyon ay may kinalaman sa dekorasyon ng mga pangalan. Kapag tumatawag sa dlsym , ipinapasa ang pangalan ng simbolo na makikita. Nangangahulugan ito na dapat itong ang linker-visible na bersyon ng pangalan, i.e. pinalamutian na pangalan.

Dahil ang proseso ng dekorasyon ay maaaring mag-iba mula sa platform hanggang sa platform at mula sa compiler hanggang sa compiler, nangangahulugan ito na halos imposibleng dynamic na makahanap ng simbolo ng C++ gamit ang isang unibersal na paraan. Kahit na nagtatrabaho ka lamang sa isang compiler at bungkalin ang panloob na mundo nito, may iba pang mga problema - bukod sa mga simpleng C-like function, mayroong isang bungkos ng iba pang mga bagay (mga virtual na talahanayan ng pamamaraan at mga katulad) na kailangang alagaan din .

Upang ibuod ang nasa itaas, kadalasan ay mas mainam na magkaroon ng isang extern na "C" na entry point na makikita ng dlsym ". Ang entry point na ito ay maaaring isang factory method na nagbabalik ng mga pointer sa lahat ng pagkakataon ng C++ class, na nagbibigay-daan sa access sa lahat ng kasiyahan ng C++.

Ang compiler ay maaaring mahusay na makitungo sa mga global na object constructor sa isang library na na-load ng dlopen , dahil mayroong ilang mga espesyal na simbolo na maaaring idagdag sa library, at kung saan ay tatawagin ng linker (kahit na sa load o execution oras) kung ang library ay dynamic na na-load o hindi na-load - kung gayon mayroong mga kinakailangang tawag sa mga konstruktor o mga destructor na maaaring mangyari dito. Sa Unix ito ang mga _init at _fini function, o para sa mga mas bagong system na gumagamit ng GNU toolkit may mga function na may label na __attribute__((constructor)) o __attribute__((destructor)) . Sa Windows, ang kaukulang function ay DllMain na may DWORD fdwReason na katumbas ng DLL_PROCESS_ATTACH o DLL_PROCESS_DETACH .

At bilang konklusyon, idaragdag namin na ang dynamic na paglo-load ay gumaganap ng isang mahusay na trabaho ng "pagbabawas ng mga paulit-ulit na pagkakataon" pagdating sa pag-instantiate ng mga template; at ang lahat ay mukhang malabo sa "pagpapaliban ng instantiation", dahil ang "pag-uugnay na yugto" ay nangyayari pagkatapos na tumakbo ang programa (at medyo posibleng sa isa pang makina na hindi nag-iimbak ng mga mapagkukunan). Sumangguni sa dokumentasyon ng compiler at linker upang makahanap ng solusyon sa sitwasyong ito.

Bukod pa rito

Ang artikulong ito ay sadyang nag-alis ng maraming detalye tungkol sa kung paano gumagana ang linker dahil naniniwala ako na ang nakasulat ay sumasaklaw sa 95% ng mga pang-araw-araw na problema na tinatalakay ng isang programmer kapag nili-link ang kanyang programa.

Kung gusto mong malaman ang higit pa, maaari kang makakuha ng impormasyon mula sa mga link sa ibaba:

Maraming salamat kina Mike Capp at Ed Wilson para sa mga kapaki-pakinabang na mungkahi tungkol sa pahinang ito.

Copyright 2004-2005,2009-2010 David Drysdale

Binibigyan ng pahintulot na kopyahin, ipamahagi at/o baguhin ang dokumentong ito sa ilalim ng mga tuntunin ng GNU Free Documentation License, Bersyon 1.1 o anumang mas bagong bersyon na inilathala ng Free Software Foundation; na walang mga Invariant na Seksyon, walang Mga Tekstong Pang-harap na Pabalat, at walang Mga Tekstong Pabalat sa Likod. Available ang isang kopya ng lisensya.

Mga Tag: Magdagdag ng mga tag

Mga Tag: Linker, linker, object file, static na library, dynamic na library, pagpapatupad ng program, kahulugan, deklarasyon

Isang baguhan na gabay sa mga linker. Bahagi 1

Pagsasalin ng artikulong Gabay ng Baguhan sa mga linker na may mga halimbawa at mga karagdagan.

Ang mga sumusunod na konsepto ay ginagamit nang palitan: linker at linker, kahulugan at kahulugan, deklarasyon at deklarasyon. Ang mga pagsingit na may mga halimbawa ay naka-highlight sa kulay abo.

Pagpangalan ng mga bahagi: kung ano ang nasa loob ng isang C file

Una, kailangan mong maunawaan ang pagkakaiba sa pagitan ng isang deklarasyon at isang kahulugan. Ang isang kahulugan ay nag-uugnay ng isang pangalan sa isang pagpapatupad ng pangalang iyon, na maaaring maging data o code:

  • Ang pagtukoy sa isang variable ay nagiging sanhi ng compiler na maglaan ng memorya para dito at posibleng punan ito ng ilang paunang halaga
  • Ang pagtukoy sa isang function ay nagiging sanhi ng compiler na makabuo ng code para sa function na iyon

Ang deklarasyon ay nagsasabi sa C compiler na sa isang lugar sa programa, marahil sa isa pang file, mayroong isang kahulugan na nauugnay sa pangalang ito (tandaan na ang kahulugan ay maaaring agad na isang deklarasyon kung saan ang kahulugan ay nasa parehong lugar).

Para sa mga variable, mayroong dalawang uri ng kahulugan

  • Mga pandaigdigang variable na umiiral para sa buhay ng programa (static na alokasyon) at karaniwang ina-access mula sa maraming mga function
  • Mga lokal na variable na umiiral lamang sa panahon ng pagpapatupad ng function kung saan ang mga ito ay ipinahayag (lokal na pagkakalagay) at naa-access lamang sa loob nito

Para sa kalinawan, "naa-access" ay nangangahulugan na ang isang variable ay maaaring i-reference ng isang pangalan na nauugnay sa kahulugan nito.

Mayroong ilang mga kaso kung saan ang mga bagay ay hindi masyadong halata.

  • Ang mga static na lokal na variable ay talagang pandaigdigan dahil umiiral ang mga ito sa buong buhay ng programa, bagama't naa-access ang mga ito sa loob ng iisang function.
  • Tulad ng mga static na variable, ang mga global na variable na naa-access lamang sa loob ng parehong file kung saan idineklara ang mga ito ay pandaigdigan din.

Ito ay nagkakahalaga kaagad na alalahanin na ang pagdedeklara ng isang function na static ay binabawasan ang saklaw nito sa file kung saan ito tinukoy (ibig sabihin, ang mga function mula sa file na ito ay maaaring ma-access ito).

Ang mga lokal at pandaigdigang variable ay maaari ding hatiin sa hindi nasimulan at nasimulan (na paunang napunan ng ilang halaga).

Pagkatapos ng lahat, maaari tayong magtrabaho sa mga variable na dynamic na nilikha gamit ang malloc function (o ang bagong operator sa C++). Imposibleng ma-access ang isang lugar ng memorya sa pamamagitan ng pangalan, kaya gumagamit kami ng mga pointer - pinangalanang mga variable na nag-iimbak ng address ng isang hindi pinangalanang lugar ng memorya. Ang lugar na ito ay maaari ding palayain gamit ang libre (o tanggalin), kaya ang memorya ay itinuturing na dynamic na inilalaan.

Pagsamahin natin ang lahat ngayon

Code Data
Global Lokal Dynamic
Sinimulan Uninitialized Sinimulan Uninitialized
Deklarasyon int fn(int x); panlabas na int x; panlabas na int x; N/A N/A N/A
Kahulugan int fn(int x) ( ... ) int x = 1;
(sa saklaw ng file)
int x;
(sa saklaw ng file)
int x = 1;
(sa saklaw ng function)
int x;
(sa saklaw ng function)
(int* p = malloc(sizeof(int));)

Mas madaling tingnan ang program na ito

/* Ito ang depinisyon ng isang uninitialized global variable */ int x_global_uninit; /* Ito ang kahulugan ng isang inisyal na global variable */ int x_global_init = 1; /* Ito ang kahulugan ng isang uninitialized global variable, ngunit maaari itong ma-access sa pamamagitan ng pangalan lamang mula sa parehong C file */ static int y_global_uninit; /* Ito ang kahulugan ng isang inisyal na global variable, ngunit maaari lamang itong ma-access sa pamamagitan ng pangalan mula sa parehong C file */ static int y_global_init = 2; /* Ito ay isang deklarasyon ng isang global variable na umiiral sa ibang lugar sa programa */ extern int z_global; /* Ito ay isang function na deklarasyon na tinukoy sa ibang lugar sa programa. Maaari mong idagdag ang extern na salita ng serbisyo. Ngunit hindi iyon mahalaga */ int fn_a(int x, int y); /* Isa itong function definition, ngunit dahil ito ay tinukoy sa salitang static, available lang ito sa parehong C file */ static int fn_b(int x) ( return x+1; ) /* Isa itong function definition . Ang mga parameter nito ay itinuturing bilang mga lokal na variable */ int fn_c(int x_local) ( /* Ito ang depinisyon ng isang uninitialized local variable */ int y_local_uninit; /* Ito ang kahulugan ng isang initialized local variable */ int y_local_init = 3; /* Ang code na ito ay tumutukoy sa mga lokal at pandaigdigang variable at function ayon sa pangalan */ x_global_uninit = fn_a(x_local, x_global_init); y_local_uninit = fn_a(x_local, y_local_init); y_local_uninit += fn_b(z_global_init); return_ (y_local, y_local_init)

Hayaang tawagin ang file na ito file.c. I-assemble natin ito ng ganito

Cc -g -O -c file.c

Kunin natin ang object file file.o

Ano ang ginagawa ng isang C compiler?

Ang trabaho ng isang C compiler ay upang gawing isang code file mula sa isang bagay na maaaring (minsan) maunawaan ng mga tao sa isang bagay na maaaring maunawaan ng isang computer. Ang output ng compiler ay isang object file, na karaniwang may extension na .o sa UNIX platform, at .obj sa Windows. Ang mga nilalaman ng isang object file ay mahalagang dalawang uri ng mga bagay

  • Mga kahulugan ng function na pagtutugma ng code
  • Ang data na nauugnay sa mga pandaigdigang variable na tinukoy sa file (kung ang mga ito ay paunang sinimulan, kung gayon ang kanilang halaga ay nakaimbak din doon)

Ang mga pagkakataon ng mga bagay na ito ay magkakaroon ng mga pangalang nauugnay sa kanila - ang mga pangalan ng mga variable o function na ang mga kahulugan ay humantong sa kanilang henerasyon.

Ang Object code ay isang pagkakasunud-sunod ng (naaangkop na naka-encode) na mga tagubilin ng makina na tumutugma sa mga tagubilin sa wikang C—lahat ng mga ifs, while, at even gotos. Ang lahat ng mga utos na ito ay gumagana sa iba't ibang uri ng impormasyon, at ang impormasyong ito ay dapat na nakaimbak sa isang lugar (nangangailangan ito ng mga variable). Bilang karagdagan, maaari nilang ma-access ang iba pang mga piraso ng code na tinukoy sa file.

Sa tuwing maa-access ng code ang isang function o variable, pinapayagan lang ito ng compiler na gawin ito kung nakita nito ang deklarasyon ng variable o function na iyon. Ang deklarasyon ay isang pangako sa compiler na mayroong isang kahulugan sa isang lugar sa programa.

Ang trabaho ng linker ay tuparin ang mga pangakong ito, ngunit ano ang dapat gawin ng compiler kapag nakatagpo ito ng mga hindi natukoy na entity?

Sa totoo lang, nag-iiwan lang ng stub ang compiler. Ang isang stub (link) ay may pangalan, ngunit ang halaga na nauugnay dito ay hindi pa alam.

Ngayon ay maaari naming halos ilarawan kung ano ang magiging hitsura ng aming programa

Pagsusuri ng file ng object

Sa ngayon kami ay nagtatrabaho sa isang abstract na programa; Ngayon ay mahalaga na makita kung ano ang hitsura nito sa pagsasanay. Sa isang UNIX platform, maaari mong gamitin ang nm utility. Sa Windows, ang katumbas ay dumpbin na may flag na /symbols, bagama't mayroon ding port ng GNU binutils na kinabibilangan ng nm.exe.

Tingnan natin kung ano ang ibinibigay sa atin ng nm para sa program na nakasulat sa itaas:

00000000 b .bss 00000000 d .data 00000000 N .debug_abbrev 00000000 N .debug_aranges 00000000 N .debug_info 00000000 N .debug000000 N rectve 00000000 r .eh_frame 00000000 r .rdata$zzz 00000000 t .text U_fn_a 00000000 T _fn_c 00000000 D _x_global_init 00000004 C _x_global_uninit U _z_global

Para sa naunang pinagsama-samang file file.o

Nm file.o

Maaaring mag-iba ang output sa bawat system, ngunit ang pangunahing impormasyon ay ang klase ng bawat karakter at ang laki nito (kung magagamit). Ang klase ay maaaring magkaroon ng mga sumusunod na halaga

  • Ang ibig sabihin ng Class U ay hindi kilala, o stub, gaya ng nabanggit sa itaas. Dalawa lang ang ganoong bagay: fn_a at z_global (ang ilang bersyon ng nm ay maaari ding mag-output ng seksyon, na sa kasong ito ay magiging *UND* o UNDEF)
  • Ang klase t o T ay nagpapahiwatig na ang code ay tinukoy - t ay lokal o T ay isang static na function. Ang .text na seksyon ay maaari ding maging output
  • Ang Class d at D ay tumutukoy sa isang inisyal na global variable, d isang lokal na variable, D isang hindi lokal na variable. Ang segment para sa variable na data ay karaniwang .data
  • Para sa mga hindi nasimulang global variable, ginagamit ang class b kung static/local o B at C kung hindi. Kadalasan ito ay segment.bss o *COM*

Mayroong iba pang mga kasuklam-suklam na klase na kumakatawan sa ilang uri ng panloob na mekanismo ng compiler.

Ano ang ginagawa ng linker? Bahagi 1

Gaya ng tinukoy namin kanina, ang pagdedeklara ng variable o function ay isang pangako sa compiler na mayroong kahulugan ng variable o function na iyon sa isang lugar, at ang trabaho ng linker ay tuparin ang mga pangakong iyon. Sa aming object file diagram maaari din itong tawaging "filling the voids".

Upang ilarawan ito, narito ang isa pang C file bilang karagdagan sa una:

/* Nagsimulang global variable */ int z_global = 11; /* Pangalawang global variable na pinangalanang y_global_init, ngunit pareho ay static */ static int y_global_init = 2; /* Deklarasyon ng isa pang global variable */ extern int x_global_init; int fn_a(int x, int y) ( return(x+y); ) int main(int argc, char *argv) ( const char *message = "Hello, world"; return fn_a(11,12); )

Hayaang tawagin ang file na ito main.c. Binubuo namin ito tulad ng dati

Cc –g –O –c main.c

Sa dalawang diagram na ito, nakikita natin ngayon na ang lahat ng mga punto ay maaaring konektado (at kung hindi, ang linker ay magtapon ng isang error). Ang bawat bagay ay may sariling lugar, at bawat lugar ay may isang bagay, at ang linker ay maaaring palitan ang lahat ng mga stub tulad ng ipinapakita sa figure.


Para sa dating pinagsama-samang main.o at file.o, pagbuo ng executable

Cc -o out.exe main.o file.o

nm output para sa executable file (out.exe sa aming kaso):

Mga simbolo mula sa sample1.exe: Pangalan Halaga Uri ng Klase Sukat ng Seksyon ng Linya _Jv_RegisterClasses | | w | NOTYPE| | |*UND* __gmon_start__ | | w | NOTYPE| | |*UND* __libc_start_main@@GLIBC_2.0| | U | FUNC|000001ad| |*UND* _init |08048254| T | FUNC| | |.init _start |080482c0| T | FUNC| | |.text __do_global_dtors_aux|080482f0| t | FUNC| | |.text frame_dummy |08048320| t | FUNC| | |.text fn_b |08048348| t | FUNC|00000009| |.text fn_c |08048351| T | FUNC|00000055| |.text fn_a |080483a8| T | FUNC|0000000b| |.pangunahing teksto |080483b3| T | FUNC|0000002c| |.text __libc_csu_fini |080483e0| T | FUNC|00000005| |.text __libc_csu_init |080483f0| T | FUNC|00000055| |.text __do_global_ctors_aux|08048450| t | FUNC| | |.text_fini |08048478| T | FUNC| | |.fini_fp_hw |08048494| R | OBJECT|00000004| |.rodata_IO_stdin_used |08048498| R | OBJECT|00000004| |.rodata __FRAME_END__ |080484ac| r | OBJECT| | |.eh_frame __CTOR_LIST__ |080494b0| d | OBJECT| | |.ctors __init_array_end |080494b0| d | NOTYPE| | |.ctors __init_array_start |080494b0| d | NOTYPE| | |.ctors __CTOR_END__ |080494b4| d | OBJECT| | |.ctors __DTOR_LIST__ |080494b8| d | OBJECT| | |.dtors __DTOR_END__ |080494bc| d | OBJECT| | |.dtors __JCR_END__ |080494c0| d | OBJECT| | |.jcr __JCR_LIST__ |080494c0| d | OBJECT| | |.jcr_DYNAMIC |080494c4| d | OBJECT| | |.dynamic na _GLOBAL_OFFSET_TABLE_|08049598| d | OBJECT| | |.got.plt __data_start |080495ac| D | NOTYPE| | |.data data_start |080495ac| W | NOTYPE| | |.data __dso_handle |080495b0| D | OBJECT| | |.data p.5826 |080495b4| d | OBJECT| | |.data x_global_init |080495b8| D | OBJECT|00000004| |.data y_global_init |080495bc| d | OBJECT|00000004| |.data z_global |080495c0| D | OBJECT|00000004| |.data y_global_init |080495c4| d | OBJECT|00000004| |.data __bss_start |080495c8| Isang | NOTYPE| | |*ABS* _edata |080495c8| Isang | NOTYPE| | |*Nakumpleto ang ABS*.5828 |080495c8| b | OBJECT|00000001| |.bss y_global_uninit |080495cc| b | OBJECT|00000004| |.bss x_global_uninit |080495d0| B | OBJECT|00000004| |.bss_end |080495d4| Isang | NOTYPE| | |*ABS*

Ang lahat ng mga simbolo mula sa parehong mga bagay ay kinokolekta dito, at lahat ng hindi natukoy na mga sanggunian ay nalinis na. Inayos din ang mga simbolo upang panatilihing magkakasama ang mga katulad na klase, at ilang karagdagang entity ang naidagdag upang matulungan ang operating system na ituring ang buong bagay bilang isang executable na programa.

Upang linisin ang output sa UNIX, maaari mong alisin ang lahat na nagsisimula sa isang underscore.

Mga dobleng character

Sa nakaraang seksyon, natutunan namin na kung ang linker ay hindi makahanap ng isang kahulugan para sa isang ipinahayag na simbolo, ito ay magtapon ng isang error. Ano ang mangyayari kung may makitang dalawang kahulugan para sa isang simbolo habang nagli-link?

Sa C++, ang lahat ay simple - ayon sa pamantayan, ang isang simbolo ay dapat palaging may isang kahulugan (ang tinatawag na isang tuntunin ng kahulugan) ng seksyon 3.2 ng pamantayan ng wika.

Para sa si, ang lahat ay hindi gaanong malinaw. Ang isang function o inisyal na global variable ay dapat palaging may isang kahulugan lamang. Ngunit ang pagtukoy sa isang hindi nasimulang pandaigdigang variable ay maaaring ituring na preliminary. C sa kasong ito ay nagbibigay-daan (hindi bababa sa hindi nagbabawal) iba't ibang mga file ng code na magkaroon ng kanilang sariling mga paunang kahulugan para sa parehong bagay.

Gayunpaman, ang mga linker ay kailangan ding harapin ang iba pang mga programming language kung saan hindi nalalapat ang one-definition na panuntunan. Halimbawa, medyo normal para sa Fortran na magkaroon ng kopya ng bawat global variable sa bawat file kung saan ito na-access. Ang linker ay pinipilit na alisin ang lahat ng mga kopya, pumili ng isa (karaniwan ay ang pinakamataas na bersyon, kung sila ay may iba't ibang laki) at itinatapon ang natitira. Ang modelong ito ay madalas na tinatawag na COMMON assembly model, dahil sa salitang FORTRAN function na COMMON.

Bilang resulta, ang UNIX linker ay karaniwang hindi nagrereklamo tungkol sa mga duplicate na kahulugan ng simbolo, kahit man lang hangga't ang duplicate na simbolo ay isang uninitialized global variable (kilala ang modelong ito bilang ang relaxed ref/def model of linking). Kung ito ay nakakaabala sa iyo (at dapat!) Hanapin sa iyong dokumentasyon ng compiler para sa isang susi na ginagawang mas mahigpit ang pag-uugali. Halimbawa, ang –fno-common para sa GNU compiler ay pinipilit ang mga uninitialized na variable na ilagay sa segment ng BSS, sa halip na bumuo ng mga karaniwang block.

Ano ang ginagawa ng operating system?

Ngayon, pagkatapos na tipunin ng linker ang maipapatupad na programa, na nag-uugnay sa lahat ng mga simbolo na may mga kinakailangang kahulugan, kailangan nating huminto ng kaunti at maunawaan kung ano ang ginagawa ng operating system kapag pinapatakbo nito ang programa.

Ang pagpapatakbo ng isang programa ay humahantong sa pagpapatupad ng machine code, kaya, malinaw naman, kailangan mong ilipat ang programa mula sa hard drive patungo sa operating memory, kung saan ang gitnang processor ay maaari nang gumana dito. Ang isang piraso ng memorya para sa isang programa ay tinatawag na isang code segment o text segment.

Ang code ay walang halaga kung walang data, kaya ang lahat ng mga global na variable ay dapat ding magkaroon ng ilang espasyo sa RAM para sa kanilang sarili. Dito mayroong pagkakaiba sa pagitan ng nasimulan at hindi nasimulang mga global na variable. Ang mga inisyal na variable ay mayroon nang sariling halaga, na nakaimbak sa parehong object at executable na mga file. Kapag nagsimula ang programa, kinopya sila mula sa hard drive papunta sa memorya sa segment ng data.

Para sa mga hindi nasimulang variable, hindi kokopyahin ng OS ang mga halaga mula sa memorya (dahil wala) at pupunuin ang lahat ng mga zero. Ang isang piraso ng memorya na nasimulan sa 0 ay tinatawag na bss segment.

Ang paunang halaga ng mga inisyal na variable ay naka-imbak sa disk, sa executable file; Para sa mga hindi nasimulang variable, ang kanilang laki ay nakaimbak.


Pakitandaan na sa lahat ng oras na ito ay pinag-uusapan lang natin ang tungkol sa mga pandaigdigang variable at hindi kailanman nabanggit ang mga lokal o dynamic na nilikha na mga bagay.

Ang data na ito ay hindi nangangailangan ng linker upang gumana dahil ang buhay nito ay magsisimula kapag nagsimula ang programa—matagal nang matapos ang linker na tumakbo. Gayunpaman, para sa kapakanan ng pagkakumpleto, muli pa rin naming ipahiwatig

  • Ang mga lokal na variable ay naninirahan sa isang piraso ng memorya na kilala bilang stack, na lumalaki at lumiliit habang ang isang function ay nagsisimula o nagtatapos sa pagpapatupad.
  • Ang dynamic na memorya ay inilalaan sa isang lugar na kilala bilang ang heap; ang pagpili ay hahawakan ng malloc function

Maaari na nating idagdag ang mga nawawalang memory area na ito sa ating diagram. Dahil parehong maaaring baguhin ng heap at stack ang kanilang laki habang tumatakbo ang program, upang ayusin ang mga problema, lumalaki sila patungo sa isa't isa. Sa ganitong paraan, ang pagkawala ng memorya ay mangyayari lamang kapag nagkita sila (at nangangailangan ito ng paggamit ng maraming memorya).


Ano ang ginagawa ng linker? Bahagi 2

Matapos matutunan ang mga pangunahing kaalaman sa kung paano gumagana ang linker, magsimula tayong sumulong at pag-aralan ang mga karagdagang kakayahan nito, sa pagkakasunud-sunod kung saan sila ay lumitaw sa kasaysayan at idinagdag sa linker.

Ang unang obserbasyon na humantong sa pagbuo ng linker: ang parehong mga seksyon ng code ay madalas na ginagamit muli (data input/output, mathematical function, file reading, atbp.). Samakatuwid, nais kong ilaan ang mga ito sa isang hiwalay na lugar at gamitin ang mga ito kasama ng maraming mga programa.

Sa pangkalahatan, ito ay sapat na madaling gamitin ang parehong object file upang bumuo ng iba't ibang mga programa, ngunit ito ay mas mahusay na mangolekta ng mga katulad na object file nang magkasama at gumawa ng isang library.

Mga static na aklatan

Ang pinakasimpleng anyo ng library ay static. Ang nakaraang seksyon ay nakasaad na maaari mo lamang ibahagi ang isang object file sa pagitan ng mga programa. Sa katotohanan, ang isang static na library ay higit pa sa isang object file.

Sa mga sistema ng UNIX, ang isang static na library ay karaniwang nabuo gamit ang ar command, at ang library file mismo ay may extension na .a. Gayundin, ang mga file na ito ay karaniwang nagsisimula sa prefix lib at ipinapasa sa linker na may –l na flag, na sinusundan ng pangalan ng library na walang lib prefix at walang extension (halimbawa, para sa file na libfred.a na idaragdag mo -lfred).

Ar rcs libfile.a file.o gcc main.o libfile.a -o out.exe

Isang mas kumplikadong halimbawa, magkaroon tayo ng tatlong file

A.c int a_f(int a) ( return a + 1; ) b.c int b_f(int a) ( return a + 1; ) c.c int c_f(int a) ( return a + 1; )

At ang pangunahing file

ABC.c #include int main() ( int a = a_f(0); int b = a_f(1); int c = a_f(2); printf("%d %d %d", a, b, c); return 0; )

Ipunin natin ang a.c, b.c at c.c sa library libabc.a. Una, i-compile natin ang lahat ng mga file (maaari mo itong gawin nang hiwalay, magkasama ito nang mas mabilis)

Gcc –g –O –c a.c b.c c.c abc.c

Makakatanggap kami ng apat na object file. Pagkatapos nito, kolektahin natin ang a, b at c sa isang file

Ar rcs libabc.a a.o b.o c.o

at ngayon ay maaari na nating i-compile ang programa

Gcc -o abc.exe libabc.a abc.o

Mangyaring tandaan na sinusubukang mag-assemble tulad nito

Gcc -o abc.exe abc.o libabc.a

ay hahantong sa isang error - ang linker ay magsisimulang magreklamo tungkol sa hindi nalutas na mga simbolo.

Sa Windows, ang mga static na aklatan ay karaniwang may extension na .lib at binuo ng LIB utility, ngunit ang nakakalito ay ang parehong extension ay nalalapat din sa mga import na aklatan, na naglalaman lamang ng listahan ng mga bagay na available sa dynamic na library (dll) .

Habang binabagtas ng linker ang koleksyon ng mga object file upang pagsama-samahin ang mga ito, nag-compile ito ng listahan ng mga simbolo na hindi pa nareresolba. Pagkatapos iproseso ang listahan ng lahat ng tahasang ipinahayag na mga bagay, ang linker ay mayroon na ngayong isa pang lugar upang maghanap ng mga hindi nadeklarang simbolo: ang library. Kung ang isang hindi nalutas na bagay ay nasa library, ito ay idinagdag nang eksakto na parang tinukoy ito ng user sa command line.

Bigyang-pansin ang antas ng detalye ng mga bagay: kung kinakailangan ang isang tiyak na simbolo, pagkatapos ay idinagdag ang buong bagay na naglalaman ng simbolong ito. Kaya, ang linker ay maaaring matagpuan ang sarili sa isang sitwasyon kung saan ito ay tumatagal ng isang hakbang pasulong at dalawang hakbang pabalik, dahil ang bagong bagay, sa turn, ay maaaring maglaman ng sarili nitong, hindi nalutas na mga simbolo.

Ang isa pang mahalagang detalye ay ang pagkakasunud-sunod ng mga pangyayari. Ang mga aklatan ay itatanong lamang pagkatapos maganap ang normal na pag-link, at ang mga ito ay pinoproseso sa pagkakasunud-sunod, mula kaliwa hanggang kanan. Nangangahulugan ito na kung ang isang library ay nangangailangan ng isang simbolo na dati ay nasa isang nakaraang konektadong library, ang linker ay hindi magagawang awtomatikong mahanap ito.

Ang isang halimbawa ay dapat makatulong upang maunawaan ito nang mas detalyado. Hayaan kaming magkaroon ng object file a.o, b.o at mga library libx.a, liby.b.

file a.o b.o libx.a liby.a
Bagay a.o b.o x1.o x2.o x3.o y1.o y2.o y3.o
Mga Kahulugan a1, a2, a3 b1, b2 x11, x12, x13 x21, x22, x23 x31, x32 y11, y12 y21, y22 y31, y32
Mga hindi natukoy na sanggunian b2, x12 a3, y22 x23, y12 y11 y21 x31

Pagkatapos iproseso ang mga file na a.o at b.o, lulutasin ng linker ang mga link na b2 at a3, na iiwan ang x12 at y22 na hindi natukoy. Sa puntong ito sinisimulan ng linker na suriin ang unang libx.a library at nalaman na maaari nitong ilabas ang x1.o, na tumutukoy sa simbolo ng x12; Kapag nagawa ito, natatanggap ng linker ang hindi natukoy na mga simbolo na x23 at y12 na idineklara sa x1.o (i.e. ang listahan ng mga hindi natukoy na simbolo ay kinabibilangan ng y22, x23 at y23).

Sinusuri pa rin ng linker ang libx.a, kaya madali nitong maresolba ang simbolo ng x23 sa pamamagitan ng paghila nito mula sa x2.o library ng libx.a. Ngunit itong x2.o ay nagdaragdag ng y11 (na ngayon ay binubuo ng y11, y22 at y12). Wala sa alinman sa mga ito ang higit pang malulutas gamit ang libx.a, kaya ang linker ay mapupunta sa liby.a.

Halos pareho ang nangyayari dito, at inilabas ng linker ang y1.o at y2.o. Ang una ay nagdaragdag ng y21, ngunit madali itong naresolba dahil ang y2.o ay naipakita na. Ang resulta ng lahat ng gawain ay nagawang lutasin ng linker ang lahat ng mga simbolo at kinuha ang halos lahat ng object file na ilalagay sa panghuling maipapatupad.

Tandaan na kung ang b.o, halimbawa, ay naglalaman ng isang link sa y32, ang lahat ay mapupunta ayon sa ibang senaryo. Ang pagpoproseso ng libx.a ay pareho sana, ngunit ang pagpoproseso ng liby.a ay naglabas ng y3.o na naglalaman ng x31 reference na tinukoy sa libx.a. Dahil tapos na sa pagproseso ang libx.a, maghahagis ng error ang linker.

Ito ay isang halimbawa ng isang circular dependency sa pagitan ng dalawang library na libx at liby.

Mga Nakabahaging Aklatan

Ang mga sikat na karaniwang aklatan ng C (karaniwan ay libc) ay may halatang kawalan - bawat maipapatupad na file ay magkakaroon ng sarili nitong kopya ng pareho. Kung ang bawat programa ay may kopya ng printf, fopen at iba pa, maraming espasyo sa disk ang masasayang.

Ang isa pa, hindi gaanong halatang kawalan ay pagkatapos ng static na pag-link, ang code ng programa ay hindi nagbabago. Kung may nakahanap at nag-aayos ng bug sa printf, ang lahat ng program na gumagamit ng library na ito ay kailangang muling itayo.

Upang malutas ang mga problemang ito, ipinakilala ang mga shared library (karaniwan ay mayroon silang extension na .so o .dll sa Windows, o .dylib sa Mac OS X). Kapag nagtatrabaho sa naturang mga aklatan, ang linker ay hindi kinakailangang pagsamahin ang lahat ng mga elemento sa isang larawan. Sa halip, nag-iiwan siya ng isang bagay tulad ng isang promissory note at ipinagpaliban ang pagbabayad kapag inilunsad ang programa.

Sa madaling salita: kung nalaman ng linker na ang isang hindi natukoy na simbolo ay nasa isang shared library, hindi ito nagdaragdag ng kahulugan sa executable. Sa halip, isinulat ng linker sa programa ang pangalan ng simbolo at ang aklatan kung saan ito diumano ay tinukoy.

Sa panahon ng pagpapatupad ng programa, tinutukoy ng operating system na ang mga nawawalang bit na ito ay naka-link "sa tamang oras." Bago patakbuhin ang pangunahing pag-andar, ang isang mas maliit na bersyon ng linker (madalas na ld.so) ay dumaan sa mga listahan ng mga may utang at kumpletuhin ang huling bahagi ng trabaho - kinukuha nito ang code mula sa library at binuo ang puzzle.

Nangangahulugan ito na walang executable program ang may kopya ng printf. Ang isang bagong itinamang bersyon ng libc ay maaari lamang palitan ang luma, at ito ay kukunin ng bawat isa sa mga programa kapag ito ay inilunsad muli.

May isa pang mahalagang pagkakaiba sa pagitan ng isang dynamic na library at isang static, at ito ay makikita sa antas ng detalye ng mga link. Kung ang isang tiyak na simbolo ay nakuha mula sa isang nakabahaging aklatan (halimbawa, printf mula sa libc), ang buong nilalaman ng aklatang iyon ay namamapa sa espasyo ng address. Ibang-iba ito sa isang static na library, kung saan ang object file lang na naglalaman ng kahulugan ng ipinahayag na simbolo ang kinukuha.

Sa madaling salita, ang isang shared library ay resulta ng isang linker (hindi lamang ar-assembled object file) na may mga naresolbang reference sa loob ng mga object sa file na iyon. Muli: matagumpay na inilalarawan ito ng nm. Para sa isang static na library, ang nm ay magpapakita ng isang set ng mga indibidwal na object file. Para sa isang shared library, ang liby.so ay tutukuyin lamang ang hindi natukoy na simbolo ng x31. Gayundin, para sa aming halimbawa sa pagkakasunud-sunod ng layout at circular reference, walang magiging problema, dahil ang lahat ng nilalaman ng y3.o at x3.o ay nakuha na.

Mayroon ding isa pang kapaki-pakinabang na tool na tinatawag na ldd. Ipinapakita nito ang lahat ng mga shared library kung saan nakasalalay ang executable o library, na may impormasyon tungkol sa kung saan ito matatagpuan. Para matagumpay na tumakbo ang programa, ang lahat ng mga aklatan na ito ay dapat matagpuan, kasama ang lahat ng kanilang mga dependency (karaniwan, sa mga sistema ng UNIX, ang loader ay naghahanap ng mga aklatan sa listahan ng mga folder, na nakaimbak sa LD_LIBRARY_PATH environment variable).

/usr/bin:ldd xeyes linux-gate.so.1 => (0xb7efa000) libXext.so.6 => /usr/lib/libXext.so.6 (0xb7edb000) libXmu.so.6 => /usr/lib /libXmu.so.6 (0xb7ec6000) libXt.so.6 => /usr/lib/libXt.so.6 (0xb7e77000) libX11.so.6 => /usr/lib/libX11.so.6 (0xb7d93000) libSM .so.6 => /usr/lib/libSM.so.6 (0xb7d8b000) libICE.so.6 => /usr/lib/libICE.so.6 (0xb7d74000) libm.so.6 => /lib/libm .so.6 (0xb7d4e000) libc.so.6 => /lib/libc.so.6 (0xb7c05000) libXau.so.6 => /usr/lib/libXau.so.6 (0xb7c01000) libxcb-xlib.so .0 => /usr/lib/libxcb-xlib.so.0 (0xb7bff000) libxcb.so.1 => /usr/lib/libxcb.so.1 (0xb7be8000) libdl.so.2 => /lib/libdl .so.2 (0xb7be4000) /lib/ld-linux.so.2 (0xb7efb000) libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0xb7bdf000)

Sa Windows, halimbawa

Ldd C:\Windows\System32\rundll32.exe ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x77100000) KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7063DLL (0x7063) => /c/WINDOWS/System32/KERNELBASE.dll (0x73e10000) apphelp.dll => /c/WINDOWS/system32/apphelp.dll (0x71ec0000) AcLayers.DLL => /c/WINDOWS/AppPatch/AcLayers.DLL (0x71ec0000) ) msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x74ef0000) USER32.dll => /c/WINDOWS/System32/USER32.dll (0x76fb0000) win32u.dll => /c/WINDOWS322 .dll (0x74060000) GDI32.dll => /c/WINDOWS/System32/GDI32.dll (0x74b00000) gdi32full.dll => /c/WINDOWS/System32/gdi32full.dll (0x041ell => SHELL (0x741) /System32/SHELL32.dll (0x74fc0000) cfgmgr32.dll => /c/WINDOWS/System32/cfgmgr32.dll (0x74900000) windows.storage.dll => /c/WINDOWS/System.System32/windows0 .dll => /c/WINDOWS/System32/combase.dll (0x76490000) ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x74100000) RPCRT4.dll => /c/WINDOWS/System32.dll (0x76b50000) bcryptPrimitives.dll => /c/WINDOWS/System32/bcryptPrimitives.dll (0x74940000) powrprof.dll => /c/WINDOWS/System32/powrprof.dll (0x703c20003) /windows 32 /advapi32.dll (0x76ad0000) sechost.dll => /c/WINDOWS/System32/sechost.dll (0x76440000) shlwapi.dll => /c/WINDOWS/System32/shlwapi.dll (0x06d) = kernel > /c/WINDOWS/System32/kernel.appcore.dll (0x73c10000) shcore.dll => /c/WINDOWS/System32/shcore.dll (0x76c20000) profapi.dll => /c/WINDOWS/System32 (profapi.dll 0x73c70000) ) Oleaut32.dll => /c/windows/system32/oleaut32.dll (0x76e20000) msvcp_win.dll => /c/windows/system32/msvcp_win.dll (0x74080000) setup /C/C/C/C/C/WIINDOM S/System32/Setupapi .dll (0x766c0000) MPR.dll => /c/WINDOWS/SYSTEM32/MPR.dll (0x6cac0000) sfc.dll => /c/WINDOWS/ SYSTEM32/sfc.dll (0x2380000) WINSPOOL.DRV => /c/WINDOWS /SYSTEM32/WINSPOOL.DRV (0x6f2f0000) bcrypt.dll => /c/WINDOWS/SYSTEM32/bcrypt.dll (0x73b70000) c/WINDOWS/SYSTEM32/sfc_os.DLL (0x68e00000) IMM32.DLL => /c/WINDOWS/System32/IMM32.DLL (0x76d90000) imagehlp.dll => /c/WINDOWS/System32/0xlp704

Ang dahilan para sa mas malaking fragmentation na ito ay dahil sa ang katunayan na ang operating system ay sapat na matalino na maaari mong i-duplicate ang disk space na may higit pa sa mga static na aklatan. Ang iba't ibang proseso ng pagpapatupad ay maaari ding magbahagi ng isang segment ng code (ngunit hindi mga segment ng data/bss). Upang gawin ito, ang buong library ay dapat na imapa sa isang pass, upang ang lahat ng mga panloob na sanggunian ay magkakahanay sa isang row: kung ang isang proseso ay na-pull out a.o at c.o, at ang pangalawa ay na-pull out b.o at c.o, walang tugma para sa operating system.


11. Mga prinsipyo ng pagpapatakbo ng mga sistema ng programming. Mga function ng mga text editor sa mga programming system. Compiler bilang isang mahalagang bahagi ng isang programming system.

Mga prinsipyo ng pagpapatakbo ng mga sistema ng programming

Mga function ng mga text editor sa mga programming system

Ang isang text editor sa isang programming system ay isang program na nagbibigay-daan sa iyong lumikha, magbago at magproseso ng mga source code ng mga programa sa mataas na antas ng mga wika.

Sa prinsipyo, lumitaw ang mga text editor nang walang anumang koneksyon sa mga tool sa pag-unlad. Nalutas nila ang mga problema sa paglikha, pag-edit, pagproseso at pag-iimbak sa panlabas na media ng anumang mga teksto na hindi kinakailangang maging mapagkukunan ng mga teksto ng mga programa sa mataas na antas ng mga wika. Maraming mga text editor ang gumaganap pa rin ng mga function na ito hanggang ngayon.

Ang paglitaw ng pinagsama-samang mga kapaligiran sa pag-unlad sa isang tiyak na yugto sa pagbuo ng mga tool sa pagbuo ng software ay naging posible na direktang isama ang mga editor ng teksto sa mga tool na ito. Sa una, ang diskarte na ito ay humantong sa katotohanan na ang gumagamit (ang nag-develop ng source program) ay nagtrabaho lamang sa kapaligiran ng text editor, nang hindi iniiwan ito upang mag-compile, mag-link, mag-download at magpatakbo ng programa para sa pagpapatupad. Para dito

Kinailangan na lumikha ng mga tool na magbibigay-daan sa pagpapakita ng progreso ng buong proseso ng pag-develop ng programa sa isang text editor na kapaligiran, tulad ng, halimbawa, isang paraan para sa pagpapakita ng mga error sa source program na nakita sa yugto ng compilation, na may pagpoposisyon sa lugar. sa teksto ng source program na naglalaman ng error.

Masasabi natin na sa pagdating ng pinagsama-samang mga kapaligiran sa pag-unlad, ang mga araw kung kailan ang mga developer ng source code ay pinilit na maghanda ng mga teksto ng programa sa papel at pagkatapos ay ipasok ang mga ito sa computer ay isang bagay ng nakaraan. Ang mga proseso ng pagsulat ng mga teksto at ang aktwal na paglikha ng software ay naging isa.

Ang pinagsama-samang mga kapaligiran sa pag-unlad ay napatunayang isang napaka-maginhawang tool. Sinimulan nilang sakupin ang merkado para sa mga tool sa pagbuo ng software. At sa kanilang pag-unlad, lumawak ang mga pagkakataong ibinigay sa developer sa kapaligiran ng text editor. Sa paglipas ng panahon, lumitaw ang mga tool para sa step-by-step na pag-debug ng mga program mula sa kanilang pinagmulang text, na pinagsasama ang mga kakayahan ng isang debugger at isang source text editor. Ang isa pang halimbawa ay isang napaka-maginhawang tool na nagbibigay-daan sa iyong graphic na i-highlight ang lahat ng mga token ng source language ayon sa kanilang mga uri sa source text ng isang program - pinagsasama nito ang mga kakayahan ng source text editor at isang compiler lexical analyzer.

Bilang isang resulta, sa mga modernong sistema ng programming, ang editor ng teksto ay naging isang mahalagang bahagi, na hindi lamang nagpapahintulot sa gumagamit na maghanda ng mga source code para sa mga programa, ngunit gumaganap din ng lahat ng mga interface at mga function ng serbisyo na ibinigay sa gumagamit ng system ng programming. At kahit na ang mga modernong developer ay maaari pa ring gumamit ng mga arbitrary na tool upang maghanda ng source code para sa mga programa, bilang panuntunan, mas gusto pa rin nilang gamitin ang text editor na kasama sa ibinigay na sistema ng programming.

Compiler bilang isang mahalagang bahagi ng isang programming system

Ang mga compiler ay, siyempre, ang mga pangunahing module sa anumang programming system. Samakatuwid, ito ay hindi nagkataon na sila ay naging isa sa mga pangunahing paksa ng pagsasaalang-alang sa aklat-aralin na ito. Kung walang compiler, walang programming system ang may katuturan, at lahat ng iba pang bahagi nito ay talagang nagsisilbi lamang sa layunin ng pagtiyak na gumagana ang compiler at gumaganap ng mga function nito.

Mula sa mga unang yugto ng pag-unlad ng mga sistema ng programming hanggang sa pagdating ng pinagsama-samang mga kapaligiran sa pag-unlad, ang mga gumagamit (mga developer ng source program) ay palaging nakikitungo sa isang compiler sa isang paraan o iba pa. Direkta silang nakipag-ugnayan dito bilang isang hiwalay na module ng software.

Ngayon, kapag nagtatrabaho sa isang programming system, ang user, bilang panuntunan, ay nakikitungo lamang sa interface nito, na kadalasan ay isang text editor na may mga advanced na function. Ang paglulunsad ng module ng compiler at lahat ng gawain nito ay awtomatikong nangyayari at nakatago mula sa user - nakikita lamang ng developer ang mga huling resulta ng pagpapatupad ng compiler. Bagama't maraming modernong sistema ng programming ang nagpapanatili ng dating kakayahan para sa direktang pakikipag-ugnayan sa pagitan ng developer at ng compiler (ito ang parehong Makefile at ang tinatawag na "command line interface"), isang makitid na bilog ng mga propesyonal lamang ang gumagamit ng mga tool na ito. Karamihan sa mga gumagamit ng mga sistema ng programming ay bihirang makipag-ugnayan nang direkta sa mga compiler.

Sa katunayan, bilang karagdagan sa pinakapangunahing compiler, na nagsasalin ng source text sa input language sa machine instruction language, karamihan sa mga programming system ay maaaring maglaman ng ilang iba pang compiler at translator. Kaya, karamihan sa mga programming system ay naglalaman ng parehong compiler mula sa assembly language at isang compiler (translator) mula sa input resource description language. Lahat sila ay bihirang direktang makipag-ugnayan sa user.

Gayunpaman, kapag nagtatrabaho sa anumang programming system, dapat mong tandaan na ang pangunahing module nito ay palaging ang compiler. Ito ay ang mga teknikal na katangian ng compiler na pangunahing nakakaimpluwensya sa kahusayan ng mga nagresultang programa na nabuo ng programming system.

Linker. Layunin at pag-andar ng linker.

12. Linker. Layunin at pag-andar ng linker

Ang linker (o link editor) ay idinisenyo upang i-link ang mga object file na binuo ng compiler, pati na rin ang mga library file na kasama sa programming system 1 .

Ang isang object file (o isang hanay ng mga object file) ay hindi maaaring isagawa hanggang ang lahat ng mga module at seksyon sa loob nito ay naka-link sa isa't isa. Ito ang ginagawa ng link editor (linker). Ang resulta ng operasyon nito ay isang solong file (madalas na tinatawag na "executable file") na naglalaman ng lahat ng text ng resultang machine code language program. Ang linker ay maaaring bumuo ng isang mensahe ng error kung nabigo itong makakita ng anumang kinakailangang mga bahagi kapag sinusubukang i-assemble ang mga object file sa isang solong kabuuan.

Ang pag-andar ng linker ay medyo simple. Sinisimulan nito ang gawain sa pamamagitan ng pagpili ng seksyon ng programa mula sa unang module ng object at pagtatalaga dito ng panimulang address. Ang mga seksyon ng programa ng mga natitirang object module ay tumatanggap ng mga address na nauugnay sa panimulang address sa sumusunod na pagkakasunud-sunod. Sa kasong ito, ang pag-andar ng pag-align ng mga panimulang address ng mga seksyon ng programa ay maaari ding maisagawa. Kasabay ng pagsasama ng mga teksto ng seksyon ng programa, ang mga seksyon ng data, mga talahanayan ng mga identifier at mga panlabas na pangalan ay pinagsama. Ang mga cross-sectional na link ay pinahihintulutan.

Ang pamamaraan para sa paglutas ng mga link ay nabawasan sa pagkalkula ng mga halaga ng address constants ng mga pamamaraan, pag-andar at mga variable, na isinasaalang-alang ang mga paggalaw ng mga seksyon na may kaugnayan sa simula ng naka-assemble na module ng programa. Kung ang mga sanggunian sa mga panlabas na variable na wala sa listahan ng mga module ng object ay nakita, ang editor ng link ay nag-aayos ng paghahanap para sa mga ito sa mga library na magagamit sa programming system. Kung ang kinakailangang bahagi ay hindi mahanap sa library, isang mensahe ng error ay nabuo.

Karaniwan, ang linker ay lumilikha ng isang simpleng software module na nilikha bilang isang yunit. Gayunpaman, sa mas kumplikadong mga kaso, ang linker ay maaaring lumikha ng iba pang mga module: mga module ng programa na may isang overlay na istraktura, mga module ng object ng mga aklatan at mga module ng mga dynamic na naka-link na mga aklatan (gumana sa mga overlay at dynamic na naka-link na mga module sa OS ay inilarawan sa unang bahagi nito. manwal).

13. Mga loader at debugger. Mga function ng bootloader

Karamihan sa mga object module sa modernong mga sistema ng programming ay binuo batay sa tinatawag na mga relative address. Ang compiler, na bumubuo ng mga object file, at pagkatapos ay ang linker, na pinagsasama ang mga ito sa isang solong kabuuan, ay hindi maaaring malaman nang eksakto kung aling tunay na lugar ng memorya ng computer ang programa ay matatagpuan sa oras ng pagpapatupad nito. Samakatuwid, hindi sila gumagana sa mga totoong address ng mga cell ng RAM, ngunit sa ilang mga kamag-anak na address. Ang nasabing mga address ay binibilang mula sa isang tiyak na kumbensyonal na punto, na kinuha bilang simula ng lugar ng memorya na inookupahan ng nagresultang programa (karaniwan ay ito ang simulang punto ng unang module ng programa).

Siyempre, walang programa ang maaaring isagawa sa mga kamag-anak na address na ito. Samakatuwid, kinakailangan ang isang module na magko-convert ng mga kamag-anak na address sa tunay (ganap) na mga address kaagad sa oras na ang programa ay inilunsad para sa pagpapatupad. Ang prosesong ito ay tinatawag na pagsasalin ng address at isinasagawa ng isang espesyal na module na tinatawag na loader.

Gayunpaman, ang boot loader ay hindi palaging isang mahalagang bahagi ng sistema ng programming, dahil ang mga pag-andar na ginagawa nito ay nakasalalay sa arkitektura ng target na sistema ng computer kung saan ang nagresultang programa na nilikha ng sistema ng programming ay naisakatuparan. Sa mga unang yugto ng pag-unlad ng OS, umiral ang mga boot loader sa anyo ng mga hiwalay na module na nagsagawa ng pagsasalin ng address at inihanda ang programa para sa pagpapatupad - na lumilikha ng tinatawag na "imahe ng gawain". Ang scheme na ito ay karaniwan para sa maraming operating system (halimbawa, RTOS sa isang computer na may uri na SM-1, OS RSX/11 o RAFOS sa isang computer na may uri na SM-4, atbp.). Ang imahe ng gawain ay maaaring i-save sa panlabas na media o nilikha muli sa bawat oras na ang programa ay inihanda para sa pagpapatupad.

Sa pagbuo ng arkitektura ng computer computing, naging posible na magsagawa ng pagsasalin ng address nang direkta sa sandaling inilunsad ang programa para sa pagpapatupad. Upang gawin ito, kinakailangang isama sa executable file ang isang kaukulang talahanayan na naglalaman ng isang listahan ng mga link sa mga address na kailangang isalin. Sa oras na inilunsad ang executable file, pinoproseso ng OS ang talahanayang ito at na-convert ang mga kamag-anak na address sa ganap na mga address. Ang scheme na ito, halimbawa, ay tipikal para sa mga operating system tulad ng MS-DOS, na laganap sa mga personal na computer. Sa scheme na ito, walang bootloader module tulad nito (sa katunayan, ito ay bahagi ng OS), at ang programming system ay responsable lamang para sa paghahanda ng talahanayan ng pagsasalin ng address - ang function na ito ay ginagampanan ng linker.

Sa modernong mga operating system, may mga kumplikadong paraan ng conversion ng address na direktang gumagana sa panahon ng pagpapatupad ng programa. Ang mga pamamaraan na ito ay batay sa mga kakayahan na binuo sa hardware ng arkitektura ng mga sistema ng computing. Ang mga paraan ng pagsasalin ng address ay maaaring batay sa segment, page at segment-page memory organization (lahat ng mga pamamaraang ito ay tinalakay sa unang bahagi ng manwal na ito). Pagkatapos, upang maisagawa ang pagsasalin ng address, ang kaukulang mga talahanayan ng system ay dapat ihanda sa oras na ilunsad ang programa. Ang mga pag-andar na ito ay ganap na nahuhulog sa mga module ng OS, kaya hindi ito ginagawa sa mga sistema ng programming.

Ang isa pang module ng programming system, ang mga function na malapit na nauugnay sa pagpapatupad ng programa, ay ang debugger.

Ang debugger ay isang software module na nagbibigay-daan sa iyong magsagawa ng mga pangunahing gawain na nauugnay sa pagsubaybay sa proseso ng pagpapatupad ng resultang application program. Ang prosesong ito ay tinatawag na pag-debug at kasama ang mga sumusunod na pangunahing tampok:


  • sunud-sunod na sunud-sunod na pagpapatupad ng nagresultang programa sa OS
    mga bagong hakbang sa mga utos ng makina o mga operator ng wika ng input;

  • pagpapatupad ng resultang programa hanggang sa maabot nito ang isa sa tinukoy
    mga lokal na breakpoint (mga address ng break);

  • pagpapatupad ng nagresultang programa bago ang paglitaw ng ilang tinukoy
    kundisyong nauugnay sa data at mga address na pinoproseso ng program na ito
    aking;

  • tinitingnan ang mga nilalaman ng mga lugar ng memorya na inookupahan ng mga utos o data
    ang resultang programa.
Sa orihinal, ang mga debugger ay mga hiwalay na software module na maaaring magproseso ng resultang programa sa mga tuntunin ng machine command language. Ang kanilang mga kakayahan ay higit na limitado sa pagmomodelo ng pagpapatupad ng mga nagresultang programa sa arkitektura ng kaukulang sistema ng computer. Ang pagpapatupad ay maaaring magpatuloy o sa mga hakbang. Ang karagdagang pag-unlad ng mga debugger ay nauugnay sa mga sumusunod na pangunahing punto:

  • ang paglitaw ng pinagsama-samang kapaligiran sa pag-unlad;

  • ang paglitaw ng suporta sa hardware para sa mga tool sa pag-debug sa marami
    mga sistema ng pag-compute.
Ang una sa mga hakbang na ito ay nagbigay-daan sa mga developer ng program na gumana hindi sa mga tuntunin ng mga tagubilin sa makina, ngunit sa mga tuntunin ng source programming language, na makabuluhang nabawasan ang mga gastos sa paggawa para sa pag-debug ng software. Kasabay nito, ang mga debugger ay hindi na naging hiwalay na mga module at naging isang pinagsama-samang bahagi ng mga programming system, dahil kailangan na nilang suportahan ang pagtatrabaho sa mga talahanayan ng pagkakakilanlan (tingnan ang seksyong "Mga talahanayan ng pagkakakilanlan. Organisasyon ng mga talahanayan ng pagkakakilanlan," Kabanata 15) at itataas mo ang kabaligtaran ng gawain sa pagkilala sa mga leksikal na yunit ng wika (tingnan ang seksyong "Pagsusuri ng semantiko at paghahanda para sa pagbuo ng code", Kabanata 14). Ito ay dahil sa ang katunayan na sa naturang kapaligiran, ang pag-debug ng programa ay nangyayari sa mga tuntunin ng mga pangalan na ibinigay ng gumagamit, at hindi sa mga tuntunin ng mga panloob na pangalan na itinalaga ng compiler. Kinakailangan din ang mga kaukulang pagbabago sa mga function ng mga linker compiler, dahil kailangan nilang magsama ng table ng mga pangalan sa komposisyon ng object at executable na mga file para maproseso ito ng debugger.

Ang pangalawang hakbang ay naging posible upang makabuluhang palawakin ang mga kakayahan ng mga tool sa pag-debug. Ngayon ay hindi na nila kailangan ang pagmomodelo ng operasyon at arkitektura ng kaukulang sistema ng computer. Ang pagpapatupad ng nagresultang programa sa debug mode ay naging posible sa parehong kapaligiran tulad ng sa normal na mode. SA zg Kasama lang sa debugger ang mga function ng paglilipat ng computer system sa naaangkop na mode bago ilunsad ang resultang program para sa pag-debug. Sa maraming paraan, ang mga function na ito ay isang priyoridad, dahil madalas silang nangangailangan ng pagtatakda ng mga talahanayan ng system at mga flag ng processor ng isang computer system.
Ang mga debugger sa modernong programming system ay mga module na may binuo na user interface na direktang gumagana sa text at mga module ng source program. Marami sa kanilang mga function ay isinama: kasama ang mga function ng source text editor na kasama sa programming system.

14. Mga subroutine na aklatan bilang mahalagang bahagi ng mga sistema ng programming

Ang mga aklatan ng mga gawain ay bumubuo ng isang mahalagang bahagi ng mga sistema ng programming.
Ang mga aklatan ng mga gawain ay naging bahagi ng mga tool sa pag-unlad mula pa noong mga unang yugto ng kanilang pag-unlad. Kahit na ang mga compiler ay nagbigay pa rin ng sarili nilang hiwalay na mga module ng programa, naiugnay na sila sa mga kaukulang aklatan, dahil ang compilation sa paanuman ay nagsasangkot ng pag-uugnay ng mga programa sa mga karaniwang function ng source language. Ang mga function na ito ay dapat na bahagi ng mga library.

Mula sa punto ng view ng isang programming system, ang mga subroutine na aklatan ay binubuo ng dalawang pangunahing bahagi. Ito ang file mismo (o isang set ng mga file ng library) na naglalaman ng object code, at isang set ng mga file na naglalarawan sa mga function, software, program, constant at variable na bumubuo sa library.

15. Lexical analysis "on the fly". Sistema ng mga pahiwatig at sanggunian.

Mga karagdagang tampok ng mga sistema ng programming

Lexical analysis on the fly. Sistema ng pahiwatig at tulong

Ang on-the-fly lexical analysis ay isang function ng isang text editor bilang bahagi ng isang programming system. Binubuo ito ng paghahanap at pag-highlight ng mga token ng input language sa text ng program nang direkta sa proseso ng paglikha nito ng developer.

Ito ay ipinatupad bilang mga sumusunod: ang developer ay lumilikha ng pinagmulang teksto ng programa (i-type ito o tinatanggap ito mula sa ibang pinagmulan), at sa parehong oras, ang programming system ay sabay-sabay na naghahanap ng mga lexemes sa tekstong ito.

Sa pinakasimpleng kaso, ang mga natukoy na lexeme ay naka-highlight lamang sa teksto gamit ang graphical na interface ng isang text editor - kulay, font, atbp. Ginagawa nitong mas madali ang gawain ng developer ng program, ginagawang mas visual ang source text at nakakatulong na makakita ng mga error sa isang napakaagang yugto - sa yugto ng paghahanda ng source code.

Sa mas binuo na mga sistema ng programming, ang mga nahanap na lexemes ay hindi lamang inilalaan sa panahon ng paghahanda ng pinagmulang teksto, ngunit inilalagay din sa talahanayan ng identifier ng compiler na kasama sa programming system. Ang diskarte na ito ay nagbibigay-daan sa iyo upang makatipid ng oras sa yugto ng compilation, dahil ang unang yugto nito - lexical analysis - ay nakumpleto na sa yugto ng paghahanda ng source text ng programa.

Ang susunod na feature ng serbisyo na ibinigay sa developer ng programming system dahil sa on-the-fly lexical analysis ay ang kakayahan ng developer na ma-access ang talahanayan ng mga identifier sa panahon ng paghahanda ng source text ng program. Maaaring turuan ng developer ang compiler na hanapin ang token na kailangan nito sa talahanayan. Ang paghahanap ay maaaring isagawa ayon sa uri o sa ilang bahagi ng impormasyon ng token (halimbawa, sa pamamagitan ng unang ilang titik). Bukod dito, ang paghahanap ay maaaring maging sensitibo sa konteksto - ang programming system ay magbibigay sa developer ng pagkakataong makahanap ng isang lexeme ng eksaktong uri na maaaring magamit sa isang partikular na lugar sa pinagmulang teksto. Bilang karagdagan sa mismong lexeme, maaaring bigyan ang developer ng ilang impormasyon tungkol dito - halimbawa, ang mga uri at komposisyon ng mga pormal na parameter para sa isang function, isang listahan ng mga magagamit na pamamaraan para sa isang uri o halimbawa ng klase. Muli nitong ginagawang mas madali ang gawain ng developer, dahil pinapawi nito ang pangangailangang alalahanin ang komposisyon ng mga function at uri ng maraming module (pangunahin ang mga library) o muling sumangguni sa dokumentasyon at impormasyon ng sanggunian.

Ang on-the-fly lexical analysis ay isang makapangyarihang function na lubos na nagpapasimple sa gawaing nauugnay sa paghahanda ng source text. Kasama ito hindi lamang sa maraming mga sistema ng programming, kundi pati na rin sa maraming mga editor ng teksto na ibinibigay nang hiwalay mula sa mga sistema ng programming (sa huling kaso, pinapayagan ka nitong mag-tune sa bokabularyo ng isang partikular na wika).

Ang isa pang maginhawang function ng serbisyo sa modernong mga sistema ng programming ay ang sistema ng mga pahiwatig at tulong. Karaniwan, naglalaman ito ng tatlong pangunahing bahagi:


  • tulong sa semantics at syntax ng input language na ginamit;

  • mga pahiwatig sa pagtatrabaho sa mismong sistema ng programming;

  • impormasyon tungkol sa mga function ng mga aklatan na kasama sa programming system
    nia.
Ang sistema ng mga pahiwatig at tulong ay kasalukuyang mahalagang bahagi ng maraming application at mga programa ng system. Bilang isang patakaran, sinusuportahan ito ng kaukulang mga utility ng OS. Samakatuwid, bukod sa iba pang mga bagay, maraming mga sistema ng programming ang may kasamang mga function ng serbisyo na nagbibigay-daan sa iyo upang lumikha at magdagdag ng isang sistema ng mga pahiwatig at tulong. Ginagawa ito upang ang developer ay makagawa at makapagpamahagi ng mga kaugnay na pahiwatig at makatulong sa kanyang mga application program.

16. Pagbuo ng mga programa sa arkitektura ng client-server

Ang istraktura ng isang application na binuo sa isang client-server architecture.

Ang pagdami ng mga dynamic na naka-link na library at mga mapagkukunan ng application program ay humantong sa isang sitwasyon kung saan ang karamihan sa mga application program ay hindi na isang solong software module, ngunit isang set ng kumplikadong magkakaugnay na mga bahagi. Marami sa mga bahaging ito ay alinman sa bahagi ng OS o nangangailangan ng kanilang paghahatid at pag-install mula sa iba pang mga developer, na kadalasan ay walang koneksyon sa mga developer ng application program mismo.

Bukod dito, sa buong hanay ng mga bahagi ng programa ng aplikasyon, dalawang lohikal na integral na bahagi ang maaaring makilala: ang una - pagbibigay ng "mas mababang antas" ng aplikasyon, na responsable para sa mga pamamaraan ng pag-iimbak, pag-access at pagbabahagi ng data; ang pangalawa ay nag-aayos ng "nangungunang antas" ng application, na kinabibilangan ng data processing logic at ang user interface.

Ang unang bahagi, bilang panuntunan, ay isang hanay ng mga bahagi ng third-party. Kadalasan ito ay sa isang paraan o iba pang konektado sa pag-access sa mga database, na maaaring magbigay para sa isang medyo kumplikadong organisasyon. Upang patakbuhin ang mga bahagi nito, kinakailangan ang isang high-performance na computing system.

Kasama sa pangalawang bahagi ang aktwal na mga algorithm, lohika at ang buong interface na nilikha ng mga developer ng programa. Nangangailangan ito ng koneksyon sa mga pamamaraan para sa pag-access sa data na nilalaman sa unang bahagi. Ang mga kinakailangan para sa mga computing system na kinakailangan upang ipatupad ang mga bahagi nito ay karaniwang mas mababa kaysa sa unang bahagi.

Pagkatapos ay lumitaw ang konsepto ng isang application na binuo sa arkitektura ng client-server. Ang unang (server) na bahagi ng naturang application ay kinabibilangan ng lahat ng mga pamamaraan na nauugnay sa pag-access ng data. Kadalasan ang mga ito ay ipinatupad ng ser-

Ver DB (data server) mula sa kaukulang DBMS (database management system) na kumpleto sa mga driver para sa pag-access dito. Kasama sa pangalawang (kliyente) na bahagi ng application ang lahat ng pamamaraan para sa pagproseso ng data at pagpapakita nito sa user. Ang bahagi ng kliyente ay nakikipag-ugnayan, sa isang banda, sa server, tumatanggap ng data mula dito, at sa kabilang banda, sa gumagamit, mga mapagkukunan ng application at OS, pagproseso ng data at pagpapakita ng mga resulta. Maaaring i-save muli ng kliyente ang mga resulta ng pagproseso sa database gamit ang mga function ng bahagi ng server.

Bilang karagdagan, sa paglipas ng panahon, ang ilan sa mga pinakakilalang kumpanya ng pagmamanupaktura ay nagsimulang mangibabaw sa merkado ng DBMS. Nag-alok sila ng mga standardized na interface para sa pag-access sa mga DBMS na kanilang nilikha. Ang mga developer ng application program, naman, ay nagsimulang tumuon sa kanila. Naimpluwensyahan din ng sitwasyong ito ang istraktura ng mga sistema ng programming. Marami sa kanila ang nagsimulang mag-alok ng mga tool na naglalayong lumikha ng mga application sa isang arkitektura ng client-server. Karaniwan, ang mga tool na ito ay ibinibigay bilang bahagi ng isang programming system at sinusuportahan ang kakayahang magtrabaho kasama ang isang malawak na hanay ng mga kilalang data server sa pamamagitan ng isa o higit pang magagamit na mga interface ng palitan ng data. Pinipili ng developer ng application program ang isa sa mga magagamit na tool kasama ang isang posibleng uri ng server (o ilang posibleng mga uri), at pagkatapos ay ang kanyang gawain ay nabawasan lamang sa paglikha ng bahagi ng kliyente; application na binuo batay sa napiling interface. Matapos malikha ang bahagi ng kliyente, maaari lamang itong gamitin at ipamahagi ng developer kasabay ng mga kaukulang tool mula sa programming system. Ang interface ng komunikasyon ay karaniwang kasama sa programming system. Karamihan sa mga sistema ng programming ay nagbibigay ng kakayahang ipamahagi ang mga paraan ng pag-access sa gilid ng server nang walang anumang karagdagang mga paghihigpit.

Tulad ng para sa bahagi ng server, dalawang paraan ang posible: ang pinakasimpleng mga server ng database ay nangangailangan ng mga developer na bumili ng mga lisensya para sa mga tool para sa paglikha at pag-debug ng mga database, ngunit madalas na pinapayagan ang mga resulta ng kanilang trabaho na maipamahagi nang walang karagdagang mga paghihigpit; Ang makapangyarihang mga server ng database, na nakatuon sa gawain ng sampu at daan-daang mga gumagamit, ay nangangailangan ng pagkuha ng mga lisensya para sa parehong paglikha at pamamahagi ng bahagi ng server ng application. Sa kasong ito, ang end user ng application ay tumatanggap ng isang buong hanay ng mga produkto ng software mula sa maraming mga developer.

Higit pang impormasyon tungkol sa pag-aayos ng mga application batay sa arkitektura ng client-server ay matatagpuan sa.

Gusto kong maunawaan nang eksakto kung anong bahagi ng compiler ng programa ang tinitingnan at isinangguni ng linker. Kaya isinulat ko ang sumusunod na code:

#isama gamit ang namespace std; #isama void FunctionTemplate (paramType val) ( i = val ) ); void Test::DefinedCorrectFunction(int val) ( i = val; ) void Test::DefinedIncorrectFunction(int val) ( i = val ) void main() ( Test testObject(1); //testObject.NonDefinedFunction(2); / /testObject.FunctionTemplate (2); }

Mayroon akong tatlong function:

  • Ang DefinedCorrectFunction ay isang normal na function, ipinahayag at tinukoy nang tama.
  • DefinedIncorrectFunction - ang function na ito ay idineklara nang tama, ngunit ang pagpapatupad ay hindi tama (nawawala;)
  • Ang NonDefinedFunction ay deklarasyon lamang. Walang definition.
  • FunctionTemplate - template ng function.

    Ngayon kung i-compile ko ang code na ito nakakakuha ako ng isang compiler error para sa nawawalang ";" sa DefinedIncorrectFunction.
    Sabihin nating ayusin ko ito at pagkatapos ay magkomento sa testObject.NonDefinedFunction(2). Ngayon nakakakuha ako ng linker error. Ngayon magkomento sa testObject.FunctionTemplate(2). Ngayon nakakakuha ako ng isang compiler error para sa nawawalang ";".

Para sa mga template ng pag-andar, ang aking pag-unawa ay hindi sila ginagalaw ng compiler maliban kung tinawag sila sa code. Kaya, ang nawawalang ";" Hindi nagrereklamo ang compiler hanggang sa tumawag ako sa testObject.FunctionTemplate(2).

Para sa testObject.NonDefinedFunction(2), hindi nagreklamo ang compiler, ngunit nagreklamo ang linker. Sa pagkakaintindi ko, dapat alam ng buong compiler na ang isang NonDefinedFunction function ay idineklara. Wala siyang pakialam sa pagpapatupad. Pagkatapos ay nagreklamo ang linker dahil hindi niya mahanap ang isang pagpapatupad. So far so good.

Kaya, hindi ko talaga maintindihan kung ano ang eksaktong ginagawa ng compiler at kung ano ang ginagawa ng linker. Ang aking pag-unawa sa mga bahagi ng tagabuo ng link sa kanilang mga tawag. Kaya kapag tinawag ang NonDefinedFunction, hinahanap nito ang pinagsama-samang pagpapatupad ng NonDefinedFunction at nagrereklamo. Ngunit ang compiler ay walang pakialam sa pagpapatupad ng NonDefinedFunction, ngunit ginawa nito para sa DefinedIncorrectFunction.

Talagang pinahahalagahan ko ito kung may makapagpaliwanag nito o magbigay ng ilang sanggunian.

8 sagot

Ang function ng compiler ay upang i-compile ang code na iyong isinusulat at i-convert ito sa mga object file. Kaya kung sakaling napalampas mo ito; o gumamit ng hindi natukoy na variable, magrereklamo ang compiler dahil ito ay mga syntax error.

Kung magtagumpay ang compilation nang walang anumang pagkabigo, .object file ay gagawin. Ang mga file ng object ay may isang kumplikadong istraktura, ngunit karaniwang naglalaman ng limang bagay

  • Mga header - impormasyon tungkol sa file
  • Object code - machine language code (ang code na ito ay hindi maaaring tumakbo nang mag-isa sa karamihan ng mga kaso)
  • Impormasyon tungkol sa paglipat. Aling mga bahagi ng code ang kailangang baguhin ang mga address kapag aktwal na naisakatuparan.
  • talahanayan ng simbolo. Ang mga character na tinutukoy ng code. Maaari silang tukuyin sa code na ito, na-import mula sa iba pang mga module, o tinukoy ng linker
  • Impormasyon sa pag-debug - ginagamit ng mga debugger

Binubuo ng compiler ang code at pinupunan ang talahanayan ng simbolo ng bawat simbolo na nakatagpo nito. Ang mga simbolo ay tumutukoy sa mga variable at function. Ang sagot sa Tanong na ito ay nagpapaliwanag sa talahanayan ng simbolo.

Naglalaman ito ng koleksyon ng executable code at data na maaaring iproseso ng linker sa isang production application o shared library. Ang isang object file ay may istraktura ng data na tinatawag na isang simbolo na talahanayan sa loob nito, na nagmamapa ng iba't ibang elemento sa object file sa mga pangalan na mauunawaan ng linker.

Punto ng tala

Kung tumawag ka ng isang function mula sa iyong code, hindi inilalagay ng compiler ang end address ng routine sa object file. Sa halip, naglalagay ito ng value ng placeholder sa code at nagdaragdag ng tala na nagsasabi sa linker na maghanap ng reference sa iba't ibang mga talahanayan ng simbolo mula sa lahat ng object file na pinoproseso nito, at ipasok ang huling lokasyon doon.

Ang mga nabuong object file ay pinoproseso ng linker, na pumupuno sa mga puwang sa mga talahanayan ng simbolo, nagli-link ng isang module sa isa pa, at sa wakas ay gumagawa ng executable code na maaaring i-load ng loader.

Kaya sa iyong partikular na kaso -

  • DefinedIncorrectFunction() - Natatanggap ng compiler ang kahulugan ng function at sinimulan itong i-compile para makagawa ng object code at ipasok ang kaukulang reference sa talahanayan ng simbolo. Nabigo ang compilation dahil sa isang syntax error, kaya ang compiler ay nag-abort na may error.
  • NonDefinedFunction() - Natatanggap ng compiler ang deklarasyon ngunit walang kahulugan, kaya nagdaragdag ito ng entry sa talahanayan ng simbolo at inilalagay ang linker upang magdagdag ng naaangkop na mga halaga (dahil ang linker ay nagpoproseso ng isang grupo ng mga object file, posible na ang kahulugan na ito ay naroroon sa ilang iba pang object file). Sa iyong kaso, hindi ka tumukoy ng anumang iba pang file, kaya nabigo ang linker na may hindi natukoy na reference sa error na NonDefinedFunction dahil hindi ito makakahanap ng reference sa kaukulang entry sa talahanayan ng simbolo.

Upang maunawaan ito, sabihin nating muli na ang iyong code ay nakaayos tulad nito

#isama #isama class Test (pribado: int i; public: Test(int val) (i=val ;) void DefinedCorrectFunction(int val); void DefinedIncorrectFunction(int val); void NonDefinedFunction(int val); template void FunctionTemplate (paramType val) ( i = val; ) );

try.cpp file

#include "try.h" void Test::DefinedCorrectFunction(int val) ( i = val; ) void Test::DefinedIncorrectFunction(int val) ( i = val; ) int main() ( Test testObject(1); testObject. NonDefinedFunction(2); //testObject.FunctionTemplate (2); bumalik 0; )

Kopyahin at tipunin muna natin ang code, ngunit huwag i-link ito

$g++ -c try.cpp -o try.o $

Ang hakbang na ito ay nagpapatuloy nang walang anumang mga problema. Kaya mayroon kang object code sa try.o. Subukan ito at ikonekta ito.

$g++ try.o try.o: Sa function `main": try.cpp:(.text+0x52): undefined reference to `Test::NonDefinedFunction(int)" collect2: ld nagbalik ng 1 exit status

Nakalimutan mong tukuyin ang Test::NonDefinedFunction. Tukuyin natin ito sa isang hiwalay na file.

File-try1.cpp

#include "try.h" void Test::NonDefinedFunction(int val) ( i = val; )

I-compile natin ito sa object code

$ g++ -c try1.cpp -o try1.o $

Muli ito ay matagumpay. Subukan nating i-link ang file na ito lamang

$ g++ try1.o /usr/lib/gcc/x86_64-redhat-linux/4.4.5/../../../../lib64/crt1.o: Sa function na `_start": (.text+ 0x20 ): hindi natukoy na sanggunian sa `pangunahing" collect2: ld ay nagbalik ng 1 exit status

Walang main kaya nanalo; t link!!

Mayroon ka na ngayong dalawang magkahiwalay na object code na mayroong lahat ng kinakailangang sangkap. Ipasa lang ang BOTH sa kanila sa linker at hayaan ang iba na gawin ito

$ g++ subukan.o subukan1.o $

Walang pagkakamali! Ito ay dahil hinahanap ng linker ang mga kahulugan ng lahat ng mga function (kahit na nakakalat sila sa iba't ibang object file) at pinupunan ang mga puwang sa object code ng mga naaangkop na halaga

Sabihin na gusto mong kumain ng sopas, kaya pumunta sa isang restaurant.

Naghahanap ka ng menu ng sopas. Kung hindi mo ito makita sa menu, umalis ka sa restaurant. (tulad ng compiler na nagrereklamo na hindi ito makahanap ng isang function). Kung makakita ka ng isa, ano ang gagawin mo?

Tawagan mo ang waiter para sumama sa iyong sopas. Gayunpaman, dahil lamang ito sa menu ay hindi nangangahulugan na mayroon din sila nito sa kusina. Siguro outdated na ang menu, baka may nakalimutan na sabihin sa chef na dapat siyang gumawa ng sopas. Kaya umalis ka ulit. (halimbawa, isang error mula sa linker na hindi nito mahanap ang simbolo)

Naniniwala ako na ito ang iyong tanong:

Kung saan ako nalilito ay noong nagreklamo ang compiler tungkol sa DefinedIncorrectFunction. Hindi ito naghanap ng pagpapatupad ng NonDefinedFunction, ngunit dumaan sa DefinedIncorrectFunction.

Sinubukan ng compiler na i-parse ang DefinedIncorrectFunction (dahil nagbigay ka ng kahulugan sa source file na iyon) at nagkaroon ng syntax error (nawawalang semicolon). Sa kabilang banda, hindi kailanman nakita ng compiler ang isang kahulugan para sa NonDefinedFunction dahil walang code sa module na iyon. Maaaring tinukoy mo ang isang kahulugan ng NonDefinedFunction sa isa pang source file, ngunit hindi ito alam ng compiler. Tinitingnan lamang ng compiler ang isang source file (at ang mga kasama nitong header file) sa isang pagkakataon.

Sinusuri ng compiler kung ang source code ay angkop para sa wika at sumusunod sa mga semantika ng wika. Ang output ng compiler ay object code.

Ang linker ay nag-uugnay sa iba't ibang object module nang magkasama upang bumuo ng isang exe. Ang mga kahulugan ng function ay matatagpuan sa yugtong ito, at ang naaangkop na code upang tawagan ang mga ito ay idinagdag sa yugtong ito.

Kino-compile ng compiler ang code sa mga unit ng pagsasalin. Isasama nito ang lahat ng code na kasama sa source na .cpp file.
Ang DefinedIncorrectFunction() ay tinukoy sa iyong source file, kaya sinusuri ito ng compiler para sa tama ng wika.
Ang NonDefinedFunction() ay may ilang kahulugan sa source file kaya hindi na kailangan ng compiler na i-compile ito, kung ang kahulugan ay naroroon sa ibang source file, ang function ay isasama bilang bahagi ng translation unit at mamaya ang linker ay magli-link dito kung sa yugto ng pag-uugnay na kahulugan ay hindi natagpuan ng linker, pagkatapos ay magtapon ito ng error sa pag-link.

Ang ginagawa ng compiler, at kung ano ang ginagawa ng linker, ay nakasalalay sa pagpapatupad: ang isang legal na pagpapatupad ay maaaring mag-imbak lamang ng tokenized na pinagmulan sa "compiler" at gawin ang lahat sa linker. Ang mga modernong pagpapatupad ay naglalagay ng higit at higit na diin sa linker, para sa mas mahusay na pag-optimize. At maraming mga naunang pagpapatupad ng template ay hindi man lang tumitingin sa template code hanggang sa oras ng sanggunian, maliban sa pagtutugma ng mga kulot na brace ay sapat na upang malaman kung saan natapos ang template. Mula sa pananaw ng isang user, mas interesado ka sa kung ang error ay nangangailangan ng "diagnosis" (na maaaring piliin ng compiler o linker) o hindi natukoy.

Sa kaso ng DefinedIncorrectFunction, ibibigay mo ang source text na kinakailangan para sa pagsusuri. Ang tekstong ito ay naglalaman ng isang error na nangangailangan ng mga diagnostic. Sa kaso ng NonDefinedFunction: kung ginagamit ang function, ang hindi pagbibigay ng kahulugan (o pagbibigay ng higit sa isang kahulugan) sa kumpletong programa ay isang paglabag sa isang panuntunan sa kahulugan, na hindi natukoy na pag-uugali. Walang kinakailangang mga diagnostic (ngunit hindi ko maisip kung alin ang hindi kasama ang ilang nawawalang kahulugan ng function na ginamit).

Sa pagsasagawa, ang mga error na madaling matukoy sa pamamagitan lamang ng pagsusuri sa text input ng isang unit ng pagsasalin ay tinutukoy ng pamantayang "kailangan ng diagnostic" at matutukoy ng compiler. Ang mga error na hindi matukoy sa pamamagitan ng pagsusuri sa isang unit ng pagsasalin (halimbawa, isang nawawalang kahulugan na maaaring naroroon sa isa pang unit ng pagsasalin) ay may pormal na hindi natukoy na gawi; sa maraming kaso ang mga error ay maaaring matukoy ng linker, at sa mga ganitong kaso ang pagpapatupad talagang nagtatapon ng isang error.

Ito ay medyo binago sa mga kaso tulad ng mga inline na function, kung saan pinapayagan kang ulitin ang kahulugan sa bawat unit ng pagsasalin, at binago ng mga template, dahil maraming mga error ang hindi matukoy hanggang sa ma-instantiate. Sa kaso ng mga template, ang standard na sheet ng pagpapatupad ay may maraming kalayaan: sa pinakamaliit, dapat i-parse ng compiler ang template nang sapat upang matukoy kung saan nagtatapos ang template. nagdagdag ng mga karaniwang bagay tulad ng typename, gayunpaman, ay nagbibigay-daan para sa higit pang pag-parse bago ang paglikha. Gayunpaman, sa mga nakadependeng konteksto, maaaring hindi matukoy ang ilang error hanggang sa magawa ang instance, na maaaring mangyari sa oras ng pag-compile o oras ng pag-link; ang mga maagang pagpapatupad ay ginustong layout ng oras ng link; ngayon ang oras ng compilation, at ginagamit ang VC++ at g++.

Ang linker (o link editor) ay idinisenyo upang iugnay ang mga object file na binuo ng compiler, pati na rin ang mga file ng library na kasama sa programming system.

Ang isang object file (o isang hanay ng mga object file) ay hindi maaaring isagawa hanggang ang lahat ng mga module at seksyon sa loob nito ay naka-link sa isa't isa. Ito ang ginagawa ng link editor (linker). Ang resulta ng trabaho nito ay isang solong file na tinatawag na boot module.

Ang load module ay isang software module na angkop para sa paglo-load at pagpapatupad, na nakuha mula sa isang object module kapag nag-e-edit ng mga link at kumakatawan sa isang programa sa anyo ng isang sequence ng machine command.

Ang linker ay maaaring bumuo ng isang mensahe ng error kung nabigo itong makakita ng anumang kinakailangang mga bahagi kapag sinusubukang i-assemble ang mga object file sa isang solong kabuuan.

Ang pag-andar ng linker ay medyo simple. Sinisimulan nito ang gawain sa pamamagitan ng pagpili ng seksyon ng programa mula sa unang module ng object at pagtatalaga dito ng panimulang address. Ang mga seksyon ng programa ng mga natitirang object module ay tumatanggap ng mga address na nauugnay sa panimulang address sa sumusunod na pagkakasunud-sunod. Sa kasong ito, ang pag-andar ng pag-align ng mga panimulang address ng mga seksyon ng programa ay maaari ding maisagawa. Kasabay ng pagsasama ng mga teksto ng seksyon ng programa, ang mga seksyon ng data, mga talahanayan ng mga identifier at mga panlabas na pangalan ay pinagsama. Ang mga cross-sectional na link ay pinahihintulutan.

Ang pamamaraan para sa paglutas ng mga link ay nabawasan sa pagkalkula ng mga halaga ng address constants ng mga pamamaraan, pag-andar at mga variable, na isinasaalang-alang ang mga paggalaw ng mga seksyon na may kaugnayan sa simula ng naka-assemble na module ng programa. Kung ang mga sanggunian sa mga panlabas na variable na wala sa listahan ng mga module ng object ay nakita, ang editor ng link ay nag-aayos ng paghahanap para sa mga ito sa mga library na magagamit sa programming system. Kung ang kinakailangang bahagi ay hindi mahanap sa library, isang mensahe ng error ay nabuo.

Karaniwan, ang linker ay lumilikha ng isang simpleng software module na nilikha bilang isang yunit. Gayunpaman, sa mas kumplikadong mga kaso, ang linker ay maaaring gumawa ng iba pang mga module: overlay-structured program modules, library object modules, at dynamic-link library modules.

Karamihan sa mga object module sa modernong mga sistema ng programming ay binuo batay sa tinatawag na mga relative address. Ang compiler, na bumubuo ng mga object file, at pagkatapos ay ang linker, na pinagsasama ang mga ito sa isang solong kabuuan, ay hindi maaaring malaman nang eksakto kung aling tunay na lugar ng memorya ng computer ang programa ay matatagpuan sa oras ng pagpapatupad nito. Samakatuwid, hindi sila gumagana sa mga totoong address ng mga cell ng RAM, ngunit sa ilang mga kamag-anak na address. Ang nasabing mga address ay binibilang mula sa isang tiyak na kumbensyonal na punto, na kinuha bilang simula ng lugar ng memorya na inookupahan ng nagresultang programa (karaniwan ay ito ang simulang punto ng unang module ng programa).

Siyempre, walang programa ang maaaring isagawa sa mga kamag-anak na address na ito. Samakatuwid, kinakailangan ang isang module na magko-convert ng mga kamag-anak na address sa tunay (ganap) na mga address kaagad sa oras na ang programa ay inilunsad para sa pagpapatupad. Ang prosesong ito ay tinatawag na pagsasalin ng address at isinasagawa ng isang espesyal na module na tinatawag na loader.

Gayunpaman, ang boot loader ay hindi palaging isang mahalagang bahagi ng sistema ng programming, dahil ang mga pag-andar na ginagawa nito ay nakasalalay sa arkitektura ng target na sistema ng computer kung saan ang nagresultang programa na nilikha ng sistema ng programming ay naisakatuparan. Sa mga unang yugto ng pag-unlad ng OS, umiral ang mga boot loader sa anyo ng mga hiwalay na module na nagsagawa ng pagsasalin ng address at inihanda ang programa para sa pagpapatupad - na lumilikha ng tinatawag na "imahe ng gawain". Ang scheme na ito ay karaniwan para sa maraming operating system (halimbawa, RTOS sa isang computer na may uri na SM-1, OS RSX/11 o RAFOS sa isang computer na may uri na SM-4, atbp.). Ang imahe ng gawain ay maaaring i-save sa panlabas na media o nilikha muli sa bawat oras na ang programa ay inihanda para sa pagpapatupad.

Sa pagbuo ng arkitektura ng computer computing, naging posible na magsagawa ng pagsasalin ng address nang direkta sa sandaling inilunsad ang programa para sa pagpapatupad. Upang gawin ito, kinakailangang isama sa executable file ang isang kaukulang talahanayan na naglalaman ng isang listahan ng mga link sa mga address na kailangang isalin. Sa oras na inilunsad ang executable file, pinoproseso ng OS ang talahanayang ito at na-convert ang mga kamag-anak na address sa ganap na mga address. Ang scheme na ito, halimbawa, ay tipikal para sa isang operating system tulad ng MS-DOS. Sa scheme na ito, walang bootloader module tulad nito (sa katunayan, ito ay bahagi ng OS), at ang programming system ay responsable lamang para sa paghahanda ng talahanayan ng pagsasalin ng address - ang function na ito ay ginagampanan ng linker.

Sa modernong mga operating system, may mga kumplikadong paraan ng conversion ng address na direktang gumagana sa panahon ng pagpapatupad ng programa. Ang mga pamamaraan na ito ay batay sa mga kakayahan na binuo sa hardware ng arkitektura ng mga sistema ng computing. Ang mga paraan ng pagsasalin ng address ay maaaring batay sa segment, page at segment-page na memory organization. Pagkatapos, upang maisagawa ang pagsasalin ng address, ang kaukulang mga talahanayan ng system ay dapat ihanda sa oras na ilunsad ang programa. Ang mga pag-andar na ito ay ganap na nahuhulog sa mga module ng OS, kaya hindi ito ginagawa sa mga sistema ng programming.

Mga artikulong babasahin:

Paano mag-upgrade sa Miui 9 Stable Global mula sa Chinese firmware? Pag-unlock ng bootloader