کد ماشین: برنامه بهشکل عدد (Machine Code)
یه خط کد رو دنبال کن که کامپایلر تبدیلش میکنه به دستورهای ساده، بعد به عددهای سادهای که پردازنده واقعاً از حافظه میخونه.
منوی دستورها
هر پردازنده یه فهرست ثابت از دستورهای ساده بلده
یه CPU نمیتونه «یه ایمیل بفرست» یا حتی «c = a + b» رو یهجا انجام بده. یه فهرست ثابت از دستورهای (Instructions) کوچیک بلده: یه مقدار رو از حافظه توی یه رجیستر بار کن، یه رجیستر رو توی حافظه بنویس، جمع کن، کم کن، مقایسه کن، به یه دستور دیگه بپر. به این فهرست میگن مجموعهدستور (Instruction Set) اون پردازنده. بیشتر کامپیوترهای شخصی از یکی به اسم x86-64 استفاده میکنن؛ گوشیها و مکهای جدیدتر از ARM. پردازندهی اسباببازی ما فقط LOAD، ADD، STORE و JUMP رو داره.
هر کاری که یه برنامه میکنه، از کشیدن یه دکمه تا پخش یه آهنگ، از میلیونها قدم ریز مثل اینها ساخته شده.
زبان اسمبلی (Assembly Language)
یه راه خوانا برای نوشتن دستورهای پردازنده، هر دستور توی یه خط، با اسمهای کوتاه بهجای عددهای خام. هر خط با یه دستور کد ماشین (Machine Code) جوره، یعنی همون عددی که CPU واقعاً میخونه. کروشه یعنی «آدرس حافظه».
LOAD R1, [20] یعنی: مقداری رو که توی آدرس ۲۰ حافظه هست توی رجیستر R1 کپی کن. STORE R3, [22] برعکسشه: R3 رو توی آدرس ۲۲ کپی کن.
خودت رو امتحان کن
توی پردازندهی اسباببازی ما، LOAD R1, [20] چیکار میکنه؟
- عدد ۲۰ رو میذاره توی رجیستر R1
- مقداری رو که توی آدرس ۲۰ حافظه ذخیره شده توی رجیستر R1 کپی میکنه
- R1 رو توی آدرس ۲۰ حافظه کپی میکنه
- میپره به دستور شمارهی ۲۰
جواب رو ببین
مقداری رو که توی آدرس ۲۰ حافظه ذخیره شده توی رجیستر R1 کپی میکنه
درسته. کروشه یعنی «آدرس»، پس هر چی توی آدرس ۲۰ ذخیره شده، که توی مثال ما ۷ـه، رو برمیداره و میذاره توی R1.
c = a + b ; a = [20], b = [21], c = [22]
LOAD R1, [20] ; R1 = 7
LOAD R2, [21] ; R2 = 5
ADD R3, R1, R2 ; R3 = 12
STORE R3, [22] ; address 22 = 12آدرس ۲۲ حالا ۱۲ رو نگه داشتهیه خط کوتاه شد چهار تا دستور: هر دو مقدار رو بیار توی رجیسترها، جمعشون کن، و جواب رو برگردون توی حافظه. یه برنامهی کامل میلیونها دستور مثل اینها داره.
خودت رو امتحان کن
دستورهای اسباببازی c = a + b رو به ترتیبی بچین که پردازنده باید اجراشون کنه. a (آدرس ۲۰) رو قبل از b (آدرس ۲۱) بار کن.
جواب رو ببین
LOAD R1, [20]
LOAD R2, [21]
ADD R3, R1, R2
STORE R3, [22]قدمبهقدم ببین

یه خط کد یه خط کد توی یه جعبه: a و b رو جمع کن و جواب رو توی c نگه دار. آدمها اینطوری مینویسنش. پردازنده هنوز نمیتونه این شکلش رو بخونه.

کامپایل میشه به چهار تا دستور یه فلش با برچسب compile به چهار تا دستور زیر هم میرسه: LOAD R1,[20]، LOAD R2,[21]، ADD R3,R1,R2 و STORE R3,[22]. کامپایلر یه خط رو شکسته به قدمهایی که پردازنده بلده.

هر دستور چهار تا بایته کنار هر دستور بایتهاش به هگزادسیمال نوشته میشه: اول شمارهی عملیات (LOAD = 01، ADD = 02، STORE = 03)، بعد رجیسترها و آدرسی که استفاده میکنه. LOAD R1,[20] میشه 01 01 14 00، چون ۲۰ توی هگز میشه 14. این کدها رو برای پردازندهی اسباببازیمون ساختیم؛ پردازندههای واقعی عددهای دیگهای دارن ولی ایده همینه.

برنامه و داده توی یه حافظه یه نوار از خونههای شمارهدار حافظه. آدرسهای ۰ تا ۱۵ شونزده بایت برنامه رو نگه میدارن، هر دستور چهار بایت. بعد از یه فاصلهی کوچیک، آدرس ۲۰ عدد ۷، آدرس ۲۱ عدد ۵ رو داره و آدرس ۲۲ هنوز معلوم نیست. برنامه و داده کنار هم نشستن، همه بهشکل عدد، منتظر پردازنده.
خودت رو امتحان کن
دستورهای یه برنامه توی یه نوع حافظهی مخصوص، جدا از دادههایی که روشون کار میکنن، نگه داشته میشن.
جواب رو ببین
نادرست
غلطه. همونطور که نوار حافظه نشون داد، دستورها و دادهها هر دو فقط بایتهایی توی یه رم مشترکن: آدرسهای ۰ تا ۱۵ برنامه رو داشتن و ۲۰ تا ۲۲ مقدارهای a و b و c رو. این همون ایدهی برنامهی ذخیرهشده از درس ۱ـه.
دو راه برای رسیدن از کد تو به کد ماشین
کامپایلر (Compiler)
کل برنامه رو از قبل به کد ماشین ترجمه میکنه و یه فایل میسازه که میشه اجراش کرد. C و Go و Rust معمولاً اینطوری کار میکنن. اجرای بعدیش سریعه، چون ترجمه از قبل انجام شده.
مفسر (Interpreter)
برنامهایه که کد تو رو میخونه و همون موقع خط به خط اجراش میکنه، بدون اینکه اول یه فایل کد ماشین بسازه. Python استاندارد اینطوری کار میکنه. خود مفسر کد ماشینیه که روی CPU اجرا میشه.
خودت رو امتحان کن
نگار برنامهی C خودش رو کامپایل میکنه. کامپایلر چی تحویل میده؟
- یه نسخهی کوتاهتر از کد C اون
- کد ماشین برای یه مجموعهدستور مشخص
- یه فهرست از متغیرهای برنامه و مقدارهاشون
- کد Python که CPU مستقیم بتونه بخونه
جواب رو ببین
کد ماشین برای یه مجموعهدستور مشخص
درسته. کامپایلر کد منبع رو تبدیل میکنه به کد ماشین، یعنی عددهای یه مجموعهدستور مشخص مثل x86-64 یا ARM.
چرا دو تا نسخهی دانلود هست
کد ماشین فقط به یه نوع چیپ میخوره
یه عدد توی مجموعهدستورهای مختلف معنیهای مختلفی داره. کد ماشینی که برای x86-64 ساخته شده برای یه پردازندهی ARM بیمعنیه، و برعکس. برای همینه که بعضی برنامهها نسخههای جدا برای ARM و x86 دارن، و برای اجرای یکی روی اون یکی به یه لایهی ترجمه، مثل Rosetta 2 اپل روی مکهای جدیدتر، نیازه.
توی پردازندهی اسباببازی ما، 02 یعنی ADD. روی یه پردازندهی دیگه همین بایت 02 ممکنه یه معنی کاملاً دیگه داشته باشه. بایتها فقط برای چیپی معنی دارن که براش نوشته شدن.
خودت رو امتحان کن
چرا برنامهای که برای یه کامپیوتر x86-64 کامپایل شده مستقیم روی یه گوشی ARM اجرا نمیشه؟
- صفحهی گوشی براش خیلی کوچیکه
- پردازندههای ARM نمیتونن برنامهی کامپایلشده اجرا کنن، فقط Python
- کد ماشینش از یه مجموعهدستور دیگهست، پس عددها برای چیپ گوشی معنی دیگهای دارن
- حافظهی داخلی گوشی از کامپیوتر کمتره
جواب رو ببین
کد ماشینش از یه مجموعهدستور دیگهست، پس عددها برای چیپ گوشی معنی دیگهای دارن
درسته. برنامه عددهاییه که برای x86-64 نوشته شده. یه چیپ ARM همون عددها رو بهشکل دستورهای دیگهای میخونه، پس یه نسخهی کامپایلشده برای ARM لازمه، یا یه لایهی ترجمه.
یادت بمونه
- CPU هیچوقت Python یا JavaScript نمیبینه. به هر زبونی که بنویسی، آخرش فقط کد ماشین اجرا میشه.
- یه خط کد ممکنه بشه کلی دستور؛ یه برنامهی کامل میلیونها دستوره.
- دستورها و دادهها هر دو فقط بایتهایی توی یه حافظهی مشترکن.
- کد ماشین مال یه مجموعهدستور مشخصه، برای همین برنامهها برای x86 و ARM جدا ساخته میشن.
مرور درس
- هر پردازنده یه مجموعهدستور ثابت از قدمهای ساده بلده: بار کردن، نوشتن، جمع، مقایسه، پرش.
- اسمبلی همین قدمها رو با اسمهای خوانا مینویسه؛ LOAD R1, [20] مقدار آدرس ۲۰ رو توی R1 کپی میکنه.
- c = a + b میشه LOAD، LOAD، ADD، STORE، و هر دستور بهشکل چند بایت کد ماشین ذخیره میشه.
- کامپایلر کد رو از قبل به کد ماشین ترجمه میکنه؛ مفسر کد رو همونطور که میخونه اجرا میکنه.
- برنامه و داده بهشکل عدد کنار هم توی حافظهن، و کد ماشین فقط برای نوع چیپ خودش معنی داره.