Pemahaman tentang bagaimana komputer diorganisasikan, bagaimana komputer tampaknya bekerja pada tingkat yang sangat rendah, diperlukan untuk memahami bagaimana program bahasa assembly bekerja. Pada tingkat yang paling sederhana, komputer memiliki tiga bagian utama:
- memori utama atau RAM yang menampung data dan instruksi,
- prosesor, yang memproses data dengan menjalankan instruksi, dan
- input dan output (kadang-kadang disingkat I/O), yang memungkinkan komputer berkomunikasi dengan dunia luar dan menyimpan data di luar memori utama sehingga bisa mendapatkan data kembali nanti.
Memori Utama
Pada kebanyakan komputer, memori dibagi menjadi byte. Setiap byte berisi 8 bit. Setiap byte dalam memori juga memiliki alamat yang merupakan angka yang menyatakan di mana byte tersebut berada dalam memori. Byte pertama dalam memori memiliki alamat 0, byte berikutnya memiliki alamat 1, dan seterusnya. Membagi memori ke dalam byte membuatnya dapat dialamati byte karena setiap byte mendapatkan alamat yang unik. Alamat memori byte tidak bisa digunakan untuk merujuk ke satu bit dari sebuah byte. Byte adalah bagian terkecil dari memori yang bisa dialamati.
Meskipun alamat merujuk ke byte tertentu dalam memori, prosesor memungkinkan penggunaan beberapa byte memori secara berurutan. Penggunaan yang paling umum dari fitur ini adalah menggunakan 2 atau 4 byte berturut-turut untuk mewakili sebuah angka, biasanya bilangan bulat. Byte tunggal kadang-kadang juga digunakan untuk merepresentasikan bilangan bulat, tetapi karena panjangnya hanya 8 bit, byte ini hanya dapat menampung 28 atau 256 kemungkinan nilai yang berbeda. Menggunakan 2 atau 4 byte dalam satu baris meningkatkan jumlah kemungkinan nilai yang berbeda menjadi 216 , 65536 atau 232 , 4294967296, masing-masing.
Ketika sebuah program menggunakan byte atau sejumlah byte dalam satu baris untuk mewakili sesuatu seperti huruf, angka, atau apa pun, byte tersebut disebut objek karena mereka semua adalah bagian dari hal yang sama. Meskipun objek semuanya disimpan dalam byte memori yang identik, mereka diperlakukan seolah-olah mereka memiliki 'tipe', yang mengatakan bagaimana byte harus dipahami: baik sebagai bilangan bulat atau karakter atau beberapa jenis lainnya (seperti nilai non-integer). Kode mesin juga dapat dianggap sebagai tipe yang diinterpretasikan sebagai instruksi. Pengertian tipe sangat, sangat penting karena mendefinisikan hal-hal apa saja yang bisa dan tidak bisa dilakukan pada objek dan bagaimana menginterpretasikan byte dari objek tersebut. Sebagai contoh, tidak sah untuk menyimpan bilangan negatif dalam objek bilangan positif dan tidak sah untuk menyimpan pecahan dalam bilangan bulat.
Alamat yang menunjuk ke (adalah alamat dari) objek multi-byte adalah alamat ke byte pertama dari objek tersebut - byte yang memiliki alamat terendah. Sebagai tambahan, satu hal penting yang perlu diperhatikan adalah bahwa Anda tidak dapat mengetahui tipe objek - atau bahkan ukurannya - dari alamatnya. Bahkan, Anda bahkan tidak dapat mengetahui tipe objek dengan melihatnya. Sebuah program bahasa assembly perlu melacak alamat memori mana yang menyimpan objek mana, dan seberapa besar objek-objek tersebut. Sebuah program yang melakukan hal tersebut adalah tipe yang aman karena hanya melakukan hal-hal pada objek yang aman untuk dilakukan pada tipe mereka. Program yang tidak melakukannya mungkin tidak akan bekerja dengan baik. Perhatikan bahwa sebagian besar program tidak benar-benar secara eksplisit menyimpan apa tipe dari sebuah obyek, mereka hanya mengakses obyek secara konsisten - obyek yang sama selalu diperlakukan sebagai tipe yang sama.
Prosesor
Prosesor menjalankan (mengeksekusi) instruksi-instruksi, yang disimpan sebagai kode mesin dalam memori utama. Selain dapat mengakses memori untuk penyimpanan, sebagian besar prosesor memiliki beberapa ruang kecil, cepat, dan berukuran tetap untuk menyimpan objek yang sedang dikerjakan. Ruang-ruang ini disebut register. Prosesor biasanya mengeksekusi tiga jenis instruksi, meskipun beberapa instruksi dapat berupa kombinasi dari jenis-jenis ini. Di bawah ini adalah beberapa contoh dari setiap tipe dalam bahasa assembly x86.
Instruksi yang membaca atau menulis memori
Instruksi bahasa assembly x86 berikut ini membaca (load) objek 2-byte dari byte di alamat 4096 (0x1000 dalam heksadesimal) ke dalam register 16-bit yang disebut 'ax':
mov ax, [1000h]
Dalam bahasa assembly ini, tanda kurung siku di sekitar angka (atau nama register) berarti bahwa angka tersebut harus digunakan sebagai alamat ke data yang harus digunakan. Penggunaan alamat untuk menunjuk ke data disebut indirection. Dalam contoh berikut ini, tanpa tanda kurung siku, register lain, bx, sebenarnya mendapatkan nilai 20 yang dimuat ke dalamnya.
mov bx, 20
Karena tidak ada indireksi yang digunakan, nilai aktual itu sendiri dimasukkan ke dalam register.
Jika operan (hal-hal yang muncul setelah mnemonic), muncul dalam urutan terbalik, instruksi yang memuat sesuatu dari memori, malah menuliskannya ke memori:
mov [1000h], ax
Di sini, memori pada alamat 1000h mendapatkan nilai ax. Jika contoh ini dieksekusi tepat setelah contoh sebelumnya, 2 byte pada 1000h dan 1001h akan menjadi integer 2 byte dengan nilai 20.
Instruksi yang melakukan operasi matematika atau logika
Beberapa instruksi melakukan hal-hal seperti pengurangan atau operasi logika seperti tidak:
Contoh kode mesin di awal artikel ini akan menjadi seperti ini dalam bahasa assembly:
tambahkan kapak, 42
Di sini, 42 dan ax ditambahkan bersama dan hasilnya disimpan kembali di ax. Dalam x86 assembly juga memungkinkan untuk menggabungkan akses memori dan operasi matematika seperti ini:
tambahkan ax, [1000h]
Instruksi ini menambahkan nilai dari bilangan bulat 2 byte yang disimpan pada 1000h ke ax dan menyimpan jawabannya di ax.
atau ax, bx
Instruksi ini menghitung atau isi dari register ax dan bx dan menyimpan hasilnya kembali ke ax.
Instruksi yang memutuskan apa instruksi berikutnya yang akan dilakukan
Biasanya, instruksi dieksekusi sesuai urutan kemunculannya di memori, yang merupakan urutan yang diketik dalam kode assembly. Prosesor hanya mengeksekusinya satu demi satu. Namun, agar prosesor dapat melakukan hal-hal yang rumit, mereka perlu mengeksekusi instruksi yang berbeda berdasarkan data yang diberikan. Kemampuan prosesor untuk mengeksekusi instruksi yang berbeda tergantung pada hasil sesuatu disebut percabangan. Instruksi yang memutuskan instruksi berikutnya disebut instruksi percabangan.
Dalam contoh ini, misalkan seseorang ingin menghitung jumlah cat yang mereka perlukan untuk mengecat persegi dengan panjang sisi tertentu. Namun, karena skala ekonomi, toko cat tidak akan menjualnya kurang dari jumlah cat yang dibutuhkan untuk mengecat persegi 100 x 100.
Untuk mengetahui jumlah cat yang mereka perlukan berdasarkan panjang persegi yang ingin mereka cat, mereka membuat serangkaian langkah ini:
- kurangi 100 dari panjang sisinya
- jika jawabannya kurang dari nol, atur panjang sisinya menjadi 100
- kalikan panjang sisi dengan dirinya sendiri
Algoritma tersebut dapat dinyatakan dalam kode berikut dimana ax adalah panjang sisinya.
mov bx, ax sub bx, 100 jge lanjutkan mov ax, 100 lanjutkan: mul ax
Contoh ini memperkenalkan beberapa hal baru, tetapi dua instruksi pertama sudah tidak asing lagi. Mereka menyalin nilai ax ke dalam bx dan kemudian mengurangi 100 dari bx.
Salah satu hal baru dalam contoh ini disebut label, sebuah konsep yang ditemukan dalam bahasa assembly pada umumnya. Label dapat berupa apa saja yang diinginkan programmer (kecuali jika itu adalah nama instruksi, yang akan membingungkan assembler). Dalam contoh ini, labelnya adalah 'continue'. Ini diinterpretasikan oleh assembler sebagai alamat dari sebuah instruksi. Dalam kasus ini, ini adalah alamat dari mult ax.
Konsep baru lainnya adalah flag. Pada prosesor x86, banyak instruksi yang menetapkan 'flag' dalam prosesor yang dapat digunakan oleh instruksi berikutnya untuk memutuskan apa yang harus dilakukan. Dalam hal ini, jika bx kurang dari 100, sub akan menetapkan flag yang mengatakan bahwa hasilnya kurang dari nol.
Instruksi berikutnya adalah jge yang merupakan kependekan dari 'Jump if Greater than or Equal to'. Ini adalah instruksi cabang. Jika flag dalam prosesor menentukan bahwa hasilnya lebih besar dari atau sama dengan nol, alih-alih hanya pergi ke instruksi berikutnya, prosesor akan melompat ke instruksi pada label continue, yaitu mul ax.
Contoh ini berfungsi dengan baik, tetapi ini bukan yang akan ditulis oleh kebanyakan programmer. Instruksi pengurangan mengatur flag dengan benar, tetapi juga mengubah nilai yang dioperasikannya, yang mengharuskan ax untuk disalin ke dalam bx. Sebagian besar bahasa assembly mengizinkan instruksi perbandingan yang tidak mengubah argumen apa pun yang dilewatkan, tetapi masih mengatur flag dengan benar dan x86 assembly tidak terkecuali.
cmp ax, 100 jge lanjutkan mov ax, 100 lanjutkan: mul ax
Sekarang, alih-alih mengurangi 100 dari ax, melihat apakah angka itu kurang dari nol, dan menetapkannya kembali ke ax, ax dibiarkan tidak berubah. Bendera-bendera masih ditetapkan dengan cara yang sama, dan lompatan masih diambil dalam situasi yang sama.
Masukan dan Keluaran
Meskipun input dan output adalah bagian mendasar dari komputasi, tidak ada satu cara untuk melakukannya dalam bahasa assembly. Hal ini karena cara kerja I/O tergantung pada pengaturan komputer dan sistem operasi yang dijalankan, bukan hanya jenis prosesor yang dimilikinya. Pada bagian contoh, contoh Hello World menggunakan panggilan sistem operasi MS-DOS dan contoh setelahnya menggunakan panggilan BIOS.
Adalah mungkin untuk melakukan I/O dalam bahasa assembly. Memang, bahasa assembly secara umum dapat mengekspresikan apa saja yang mampu dilakukan oleh komputer. Akan tetapi, walaupun ada instruksi untuk menambah dan mencabang dalam bahasa assembly yang akan selalu melakukan hal yang sama, tidak ada instruksi dalam bahasa assembly yang selalu melakukan I/O.
Hal penting yang perlu diperhatikan adalah, bahwa cara kerja I/O bukanlah bagian dari bahasa assembly apa pun, karena ini bukan bagian dari cara kerja prosesor.