Jawaban lain hanya mempertimbangkan NOP yang sebenarnya dieksekusi di beberapa titik - yang digunakan cukup umum, tetapi itu bukan satu-satunya penggunaan NOP.
The non-melaksanakan NOP juga cukup berguna ketika menulis kode yang dapat ditambal - pada dasarnya, Anda akan pad fungsi dengan beberapa NOP setelah itu RET
(atau instruksi serupa). Ketika Anda harus menambal executable, Anda dapat dengan mudah menambahkan lebih banyak kode ke fungsi mulai dari yang asli RET
dan menggunakan sebanyak mungkin NOP yang Anda butuhkan (misalnya untuk lompat jauh atau bahkan kode inline) dan menyelesaikan dengan yang lain RET
.
Dalam kasus penggunaan ini, noone pernah mengharapkan NOP
untuk mengeksekusi. Satu-satunya titik adalah untuk memungkinkan menambal executable - dalam dieksekusi non-empuk teoretis, Anda harus benar-benar mengubah kode fungsi itu sendiri (kadang-kadang mungkin sesuai dengan batas asli, tetapi cukup sering Anda akan memerlukan lompatan pula ) - itu jauh lebih rumit, terutama mempertimbangkan perakitan yang ditulis secara manual atau kompilator yang mengoptimalkan; Anda harus menghormati lompatan dan konstruksi serupa yang mungkin menunjuk pada beberapa kode penting. Secara keseluruhan, cukup rumit.
Tentu saja, ini jauh lebih banyak digunakan di masa lalu, ketika berguna untuk membuat tambalan seperti ini kecil dan online . Hari ini, Anda hanya akan mendistribusikan biner yang dikompilasi dan selesai dengan itu. Masih ada beberapa yang menggunakan patching NOP (mengeksekusi atau tidak, dan tidak selalu huruf NOP
s - misalnya, Windows menggunakan MOV EDI, EDI
untuk patching online - itu adalah jenis di mana Anda dapat memperbarui perpustakaan sistem saat sistem benar-benar berjalan, tanpa perlu restart).
Jadi pertanyaan terakhir adalah, mengapa memiliki instruksi khusus untuk sesuatu yang tidak benar-benar melakukan apa-apa?
- Ini adalah instruksi aktual - penting saat debugging atau handcoding assembly. Instruksi seperti
MOV AX, AX
akan melakukan hal yang persis sama, tetapi jangan memberi sinyal maksud dengan jelas.
- Padding - "kode" yang ada di sana hanya untuk meningkatkan kinerja keseluruhan kode yang tergantung pada perataan. Itu tidak pernah dimaksudkan untuk dieksekusi. Beberapa pengadu hanya menyembunyikan bantalan NOP di pembongkaran mereka.
- Ini memberi lebih banyak ruang untuk mengoptimalkan kompiler - pola yang masih digunakan adalah Anda memiliki dua langkah kompilasi, yang pertama agak sederhana dan menghasilkan banyak kode perakitan yang tidak perlu, sedangkan yang kedua membersihkan, mengembalikan referensi alamat dan menghapus instruksi asing. Ini sering terlihat dalam bahasa-bahasa yang dikompilasi JIT juga - keduanya .NET's IL dan JVM's byte-code menggunakan
NOP
cukup banyak; kode rakitan terkompilasi yang sebenarnya tidak lagi memilikinya. Perlu dicatat bahwa itu bukan x86 NOP
.
- Itu membuat debugging online lebih mudah baik untuk membaca (memori pre-zeroed adalah all-
NOP
s, membuat pembongkaran jauh lebih mudah untuk dibaca) dan untuk hot-patching (meskipun saya sejauh ini lebih suka Edit dan Lanjutkan di Visual Studio: P).
Untuk menjalankan NOP, tentu saja ada beberapa poin lagi:
- Performa, tentu saja - ini bukan alasannya pada 8085, tetapi bahkan 80486 sudah memiliki eksekusi instruksi pipa, yang membuat "tidak melakukan apa-apa" sedikit lebih rumit.
- Seperti yang terlihat
MOV EDI, EDI
, ada NOP lain yang efektif selain literal NOP
. MOV EDI, EDI
memiliki kinerja terbaik sebagai NOP 2-byte pada x86. Jika Anda menggunakan dua NOP
s, itu akan menjadi dua instruksi untuk dieksekusi.
EDIT:
Sebenarnya, diskusi dengan @DmitryGrigoryev memaksa saya untuk memikirkan hal ini sedikit lebih banyak, dan saya pikir ini merupakan tambahan yang berharga untuk pertanyaan / jawaban ini, jadi izinkan saya menambahkan beberapa bit tambahan:
Pertama, poin, jelas - mengapa ada instruksi yang melakukan sesuatu seperti mov ax, ax
? Sebagai contoh, mari kita lihat kasus 8086 kode mesin (lebih tua dari 386 kode mesin):
- Ada instruksi NOP khusus dengan opcode
0x90
. Ini masih saat ketika banyak orang menulis dalam pertemuan, ingatlah. Jadi, bahkan jika tidak ada NOP
instruksi khusus , NOP
kata kunci (alias / mnemonic) akan tetap berguna dan akan memetakannya.
- Instruksi seperti
MOV
benar - benar memetakan ke banyak opcode yang berbeda, karena menghemat waktu dan ruang - misalnya, mov al, 42
adalah "pindahkan byte langsung ke al
register", yang diterjemahkan menjadi 0xB02A
( 0xB0
menjadi opcode, 0x2A
menjadi argumen "langsung"). Jadi itu membutuhkan dua byte.
- Tidak ada opcode pintas untuk
mov al, al
(karena pada dasarnya itu hal yang bodoh untuk dilakukan), jadi Anda harus menggunakan mov al, rmb
overload (rmb "register or memory"). Itu sebenarnya membutuhkan tiga byte. (walaupun mungkin akan menggunakan yang kurang spesifik mov rb, rmb
, yang seharusnya hanya membutuhkan dua byte mov al, al
- byte argumen digunakan untuk menentukan sumber dan register target; sekarang Anda tahu mengapa 8086 hanya memiliki 8 register: D). Bandingkan dengan NOP
, yang merupakan instruksi byte tunggal! Ini menghemat memori dan waktu, karena membaca memori pada 8086 masih cukup mahal - belum lagi memuat program itu dari kaset atau disket atau sesuatu, tentu saja.
Jadi dari mana datangnya xchg ax, ax
? Anda hanya perlu melihat opcodes dari xhcg
instruksi lainnya . Anda akan melihat 0x86
, 0x87
dan akhirnya, 0x91
- 0x97
. Jadi nop
dengan itu 0x90
sepertinya cocok untuk xchg ax, ax
(yang, sekali lagi, bukan xchg
"kelebihan" - Anda harus menggunakan xchg rb, rmb
, pada dua byte). Dan pada kenyataannya, saya cukup yakin ini adalah efek samping yang bagus dari arsitektur mikro saat itu - jika saya ingat dengan benar, mudah untuk memetakan seluruh jajaran 0x90-0x97
"xchg, bertindak atas register ax
dan ax
- di
" ( operan menjadi simetris, ini memberi Anda jangkauan penuh, termasuk nop xchg ax, ax
; perhatikan bahwa pesanannya adalah ax, cx, dx, bx, sp, bp, si, di
- bx
setelah dx
,ax
; ingat, nama register adalah mnemonik, bukan nama yang dipesan - akumulator, penghitung, data, basis, penunjuk tumpukan, penunjuk dasar, indeks sumber, indeks tujuan). Pendekatan yang sama juga digunakan untuk operan lain, misalnya mov someRegister, immediate
set. Di satu sisi, Anda bisa menganggap ini seolah-olah opcode sebenarnya bukan byte penuh - beberapa bit terakhir adalah "argumen" untuk operan "nyata".
Semua ini dikatakan, pada x86, nop
dapat dianggap sebagai instruksi nyata, atau tidak. Mikro-arsitektur asli memang memperlakukannya sebagai varian xchg
jika saya ingat dengan benar, tetapi sebenarnya disebutkan nop
dalam spesifikasi. Dan karena xchg ax, ax
tidak benar-benar masuk akal sebagai instruksi, Anda dapat melihat bagaimana para perancang 8086 menghemat transistor dan jalur dalam decoding instruksi dengan mengeksploitasi fakta yang 0x90
memetakan secara alami ke sesuatu yang sepenuhnya "noppy".
Di sisi lain, i8051 memiliki opcode yang sepenuhnya dirancang untuk nop
- 0x00
. Agak praktis. Desain instruksi pada dasarnya menggunakan nibble tinggi untuk operasi dan nibble rendah untuk memilih operan - misalnya, add a
adalah 0x2Y
, dan 0xX8
berarti "register 0 direct", demikian 0x28
juga add a, r0
. Menghemat banyak pada silikon :)
Saya masih bisa melanjutkan, karena desain CPU (belum lagi desain kompiler dan desain bahasa) adalah topik yang cukup luas, tapi saya pikir saya telah menunjukkan banyak sudut pandang berbeda yang masuk ke dalam desain dengan cukup baik.