19-bo‘lim
Manba kodidan mashina kodigacha
Kompilyatsiyaning to'rt bosqichi, C kodi assemblerga qanday aylanadi, optimallashtirish nima qiladi va bog'lovchining vazifasi.
Ushbu bo‘lim mundarijasi
Yozgan kodingiz protsessorga yetib borguncha bir necha bosqichdan o'tadi. Bu bo'limda o'sha yo'lni haqiqiy vositalar bilan ko'rib chiqamiz.
To'rt bosqich #
Har bosqichni alohida bajarib ko'rish mumkin:
gcc -E d.c -o d.i # 1. preprotsessor
gcc -S d.i -o d.s # 2. kompilyator
gcc -c d.s -o d.o # 3. assembler
gcc d.o -o d # 4. bog'lovchi
Har bosqich nima qilganini raqamlarda ko'ramiz. Manba fayl atigi 8 qator edi:
| Bosqich | Fayl | Natija |
|---|---|---|
| Asl manba | d.c | 8 qator |
| Preprotsessordan keyin | d.i | 100 dan ortiq qator |
Farq qayerdan keldi? #include <stdio.h> qatori butun bir
sarlavha faylini ichiga ko'chirdi - undagi yuzlab e'lon
bilan birga.
Preprotsessorning o'zi C tilini umuman tushunmaydi. U
faqat matn almashtiradi: #include ni fayl mazmuniga,
#define ni uning qiymatiga.
Buni ko'rish oson - preprotsessordan keyin main shunday
ko'rinadi:
int main(void) {
printf("%s, arxitektura!\n", "Salom");
return 0;
}
#define bilan e'lon qilingan nom o'z qiymatiga
almashtirilgan, #include esa yuqorida ochilgan.
Diqqat qiling: SALOM makrosi preprotsessordan keyin
yo'qolgan - uning o'rnida "Salom" matni turibdi.
Preprotsessor shunchaki matn almashtiradi, C tilini
tushunmaydi ham.
| Bosqich | Kirish | Chiqish | Vositasi |
|---|---|---|---|
| Preprotsessor | .c | .i | cpp |
| Kompilyator | .i | .s | cc1 |
| Assembler | .s | .o | as |
| Bog'lovchi | .o + kutubxonalar | bajariluvchi | ld |
Odatda gcc hammasini bir chaqiruvda bajaradi, shuning
uchun bu bosqichlar ko'rinmaydi.
Sizning .o faylingizda printf yo'q - faqat unga
havola bor.
Bog'lovchi shu havolani haqiqiy manzil bilan almashtiradi:
| Turi | Qachon | Natija |
|---|---|---|
| Statik | Yig'ish paytida | Kutubxona fayl ichiga kiradi |
| Dinamik | Ishga tushirishda | Tizimdagi .so fayl ishlatiladi |
Linux da printf odatda dinamik bog'lanadi - shuning uchun
kichik dastur ham libc.so ga muhtoj bo'ladi.
12-bo'limdagi assembler dasturimizda esa kutubxona umuman yo'q edi - u to'g'ridan-to'g'ri yadroga murojaat qildi. Shuning uchun u hech narsaga bog'liq emas.
Konteynerlar bilan ishlaganda bu farq muhim bo'ladi: statik bog'langan dastur bo'sh konteynerda ham ishlaydi.
Kompilyator nima qiladi? #
Endi eng qiziq bosqich - C kodi assemblerga aylanishi. Bir xil funksiyani ikki xil optimallashtirish darajasida ko'ramiz:
Bir xil C funksiyasini ikki xil optimallashtirish darajasida kompilyatsiya qilamiz:
int kvadratlar_yigindisi(int n) {
int jami = 0;
for (int i = 1; i <= n; i++) {
jami += i * i;
}
return jami;
}
Natijalarni solishtiramiz:
| Daraja | Komandalar | Nishonlar | Xotiraga murojaat |
|---|---|---|---|
-O0 (optimallashtirishsiz) | 16 | 3 | 9 |
-O2 (optimallashtirilgan) | 16 | 3 | 0 |
Komandalar soni bir xil. Farq faqat oxirgi ustunda - va u hal qiluvchi.
-O0 da har o'zgaruvchi xotirada saqlanadi va har
murojaatda o'qib-yoziladi:
kvadratlar_yigindisi:
push rbp
mov rbp, rsp
mov DWORD PTR -20[rbp], edi
mov DWORD PTR -4[rbp], 0
mov DWORD PTR -8[rbp], 1
jmp .L2
.L3:
mov eax, DWORD PTR -8[rbp]
imul eax, eax
-O2 da esa hamma narsa registrda qoladi:
kvadratlar_yigindisi:
test edi, edi
jle .L4
add edi, 1
mov eax, 1
xor edx, edx
.L3:
mov ecx, eax
imul ecx, eax
add eax, 1
add edx, ecx
cmp eax, edi
jne .L3
mov eax, edx
ret
[rbp] ko'rinishidagi murojaatlar - bu xotira. Ikkinchi
ro'yxatda ular umuman yo'q.
9-bo'limda ko'rgan edik: registrga murojaat bir takt, xotiraga esa yuzlab takt. Kompilyator aynan shu farqni yo'q qildi.
Komandalar soni bir xil chiqdi. Lekin uchinchi ustunga
qarang: xotiraga murojaat -O0 da to'qqizta, -O2 da
nolta.
Bu 9 va 14-bo'limlardagi bilimning amaliy tasdig'i: optimallashtiruvchi kompilyatorning asosiy ishi - o'zgaruvchilarni registrda ushlab turish.
| Daraja | Nima qiladi |
|---|---|
-O0 | Har o'zgaruvchi xotirada; nosozlik qidirish uchun qulay |
-O2 | Registrlar, halqa optimallashtirish, ortiqchani olib tashlash |
-O0 dagi mov DWORD PTR -4[rbp], eax - bu "stekdagi
o'zgaruvchiga yozish". -O2 da esa o'sha o'zgaruvchi butun
halqa davomida edx registrida qoladi.
godbolt.org - eng foydali o'quv vositasiCompiler Explorer sayti yozgan kodingiz qanday assemblerga aylanishini darhol ko'rsatadi.
U bilan quyidagilarni sinab ko'ring:
| Tajriba | Nimani ko'rasiz |
|---|---|
-O0 va -O2 ni solishtirish | Optimallashtirish kuchi |
if o'rniga arifmetika | Shoxsiz kod (16-bo'lim) |
x * 8 va x << 3 | Kompilyator o'zi surishga aylantiradi (4-bo'lim) |
| Halqa ichidagi doimiy | U halqadan tashqariga chiqariladi |
Bu "kompilyator meni tushundimi?" degan savolga eng tez javob beradigan usul.
-O2 nosozlik qidirishni qiyinlashtiradiOptimallashtirilgan kodda:
| Muammo | Sabab |
|---|---|
| O'zgaruvchi "ko'rinmaydi" | U registrda, xotirada yo'q |
| Qatorlar tartibi chalkash | Komandalar qayta tartiblangan |
| Funksiya yo'qolgan | U chaqiruv joyiga ko'chirilgan (inline) |
| Halqa boshqacha ko'rinadi | Yoyilgan yoki birlashtirilgan |
Shuning uchun nosozlik qidirishda -Og ishlatiladi: u
ba'zi optimallashtirishlarni qoldiradi, lekin kodni
o'qilishli saqlaydi.
Ishlab chiqarishda esa -O2 yoki -O3. -O3 har doim ham
tezroq emas - u kodni kattalashtiradi va kesh (14-bo'lim)
ga salbiy ta'sir qilishi mumkin. O'lchab ko'ring.
Optimallashtirish turlari #
Kompilyator o'nlab usul qo'llaydi. Eng muhimlari:
| Usul | Nima qiladi |
|---|---|
| Registrga joylashtirish | O'zgaruvchini xotirada emas, registrda saqlash |
| O'lik kodni olib tashlash | Hech qachon ishlatilmaydigan kod |
| Doimiylarni oldindan hisoblash | 2 * 3 o'rniga 6 |
| Funksiyani ichkariga ko'chirish | Chaqiruv o'rniga tanasi |
| Halqani yoyish | Takrorlashlar sonini kamaytirish |
| Kuchni pasaytirish | x * 8 o'rniga x << 3 |
Oxirgi ikkitasi 4 va 16-bo'limlardagi mavzularga bevosita bog'liq.
C va C++ da ba'zi amallar aniqlanmagan xulq (undefined behaviour) hisoblanadi: butun son toshishi, bo'sh ko'rsatkichni o'qish, chegaradan chiqish.
Kompilyator bunday holat hech qachon bo'lmaydi deb faraz qiladi - va shu farazga tayanib optimallashtiradi.
Natijada:
| Kod | -O0 da | -O2 da |
|---|---|---|
| Toshish tekshiruvi | Ishlaydi | Olib tashlanishi mumkin |
| Bo'sh ko'rsatkich tekshiruvi | Ishlaydi | Olib tashlanishi mumkin |
Bu nazariy muammo emas: Linux yadrosida shunday xato bo'lgan - kompilyator bo'sh ko'rsatkich tekshiruvini "keraksiz" deb olib tashlagan va bu zaiflikka olib kelgan.
Xulosa: aniqlanmagan xulqdan qoching.
-fsanitize=undefined bayrog'i bilan sinab ko'ring - u
bunday joylarni topadi.
3-bo'limdagi butun son toshishi haqidagi ogohlantirish aynan shu mavzuning bir qismi.
- To'rt bosqichni yoddan tartib bilan yozing.
- Har bosqich qanday fayl chiqarishini ayting.
gcc -Ebilan o'z faylingizni preprotsessordan o'tkazing.- Natijada necha qator chiqdi? Aslida nechta edi?
gcc -Sbilan assembler kodini oling va o'qing.-O0va-O2chiqishlarini solishtiring - qaysi biri qisqa?[rbp]ko'rinishidagi murojaatlar nimani anglatishini yozing.-O2da ular nima uchun yo'qolganini tushuntiring.godbolt.orgda bir funksiyani turli darajalarda sinang.- Statik va dinamik bog'lash farqini
lddbilan ko'ring.
Xulosa #
- Kompilyatsiya to'rt bosqich: preprotsessor, kompilyator, assembler, bog'lovchi.
- Preprotsessor shunchaki matn almashtiradi - C tilini tushunmaydi.
#include <stdio.h>sakkiz qatorlik faylni yuzlab qatorga aylantiradi.- Bog'lovchi
printfkabi havolalarni haqiqiy manzil bilan almashtiradi. - Bog'lash statik (fayl ichiga) yoki dinamik (ishga tushirishda) bo'ladi.
-O0da har o'zgaruvchi xotirada,-O2da esa registrda.- Bizning misolda
-O2xotiraga murojaatni to'qqizdan nolga tushirdi. - Asosiy optimallashtirishlar: registrga joylashtirish, o'lik kod, inline, halqa yoyish.
-O3har doim ham-O2dan tez emas - kod kattalashib keshga zarar berishi mumkin.- Aniqlanmagan xulq kompilyatorga xavfli farazlar qilishga ruxsat beradi.
Keyingi, yakuniy bo'limda 13-bo'limdagi mashinamizga assembler yozamiz va butun zanjirni yopamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.