Інші відповіді розглядають лише NOP, який фактично виконується в якийсь момент - це використовується досить часто, але це не єдине використання NOP.
Невиконання NOP також дуже корисно при написанні коду , який може бути виправлений - в основному, ви будете подушечка функція з кілька НОПОМ після на RET
(або аналогічної інструкції). Коли вам доведеться виправити виконувану програму, ви можете легко додати більше коду до функції, починаючи з оригіналу RET
та використовуючи стільки тих NOP, скільки вам потрібно (наприклад, для стрибків у довгі або навіть вбудований код) і закінчуючи іншим RET
.
У цьому випадку використання ніколи не очікує NOP
виконання. Єдиний момент - дозволити виправлення виконуваного файлу - у теоретичному непідставному виконуваному файлі вам доведеться фактично змінити код самої функції (іноді це може відповідати початковим межам, але досить часто вам знадобиться стрибок у будь-якому випадку ) - це набагато складніше, особливо враховуючи складену вручну збірку або оптимізуючий компілятор; ви повинні поважати стрибки та подібні конструкції, які, можливо, вказували на якийсь важливий фрагмент коду. Загалом, досить хитро.
Звичайно, це було набагато більше застосовувалося в старі часи, коли корисно було робити патчі на зразок цих маленьких і Інтернеті . Сьогодні ви просто розповсюдите перекомпільований двійковий файл і будете робити з ним. Є ще деякі, хто використовує патчі NOP-файлів (виконуючи чи ні, і не завжди буквений NOP
s - наприклад, Windows використовує MOV EDI, EDI
для інтернет-патчі - саме такий спосіб можна оновити системну бібліотеку, поки система насправді працює, не потребуючи перезапуску).
Отже, останнє запитання: чому мати спеціальну інструкцію для чогось, що насправді нічого не робить?
- Це фактична інструкція - важлива під час налагодження або складання ручного кодування. Інструкції, як
MOV AX, AX
будуть робити точно так само, але не сигналізують про наміри настільки чітко.
- Padding - "код", який існує тільки для покращення загальної продуктивності коду, що залежить від вирівнювання. Це ніколи не призначено стратити. Деякі налагоджувачі просто приховують прокладки NOP під час їх розбирання.
- Це дає більше місця для оптимізації компіляторів - все ще застосовується схема полягає в тому, що у вас є два етапи компіляції, перший досить простий і створює безліч непотрібних кодів збірки, а другий очищає, перенаправляє посилання на адреси та видаляє сторонні інструкції. Це часто спостерігається і в мовах, що складаються з JIT, - і IL, і байт-код JVM використовують
NOP
досить багато; фактично складений код складання більше не має цих. Слід зазначити, що це не x86- NOP
і.
- Це робить налагодження в Інтернеті простішим як для читання (попередньо нульова пам'ять буде загальною
NOP
, що робить розбирання набагато простішим для читання), так і для гарячого виправлення (хоча я на сьогоднішній день віддаю перевагу "Редагувати та продовжувати" у Visual Studio: P).
Для виконання НОП, звичайно, є ще кілька пунктів:
- Продуктивність, звичайно - це не тому, що це було в 8085 році, але навіть 80486 вже мав конвеєрне виконання інструкцій, що робить "нічого не робити" трохи складніше.
- Як бачимо з
MOV EDI, EDI
, існують інші ефективні НОП, крім буквальних NOP
. MOV EDI, EDI
має найкращу продуктивність як 2-байтний NOP на x86. Якщо ви використовували два NOP
s, це були б дві інструкції для виконання.
Редагувати:
Власне, обговорення з @DmitryGrigoryev змусило мене ще трохи подумати над цим питанням, і я думаю, що це цінне доповнення до цього питання / відповіді, тому дозвольте мені додати ще декілька біт:
По-перше, вкажіть, очевидно, - чому б існувала інструкція, яка робить щось подібне mov ax, ax
? Наприклад, давайте розглянемо випадок машинного коду 8086 (старший навіть 386 машинного коду):
- Існує спеціальна інструкція щодо NOP з кодом
0x90
. Це ще час, коли багато людей писали на зборах, пам’ятайте. Тож навіть якщо б не було спеціальної NOP
інструкції, NOP
ключове слово (псевдонім / мнемонічний) все-таки було б корисним і відображало б це.
- Інструкції на зразок
MOV
насправді відображають багато різних опкодів, тому що це економить час і простір - наприклад, mov al, 42
"перемістити негайний байт до al
реєстру", що перекладається 0xB02A
( 0xB0
опкод,0x2A
будучи "безпосереднім" аргументом). Отже, це займає два байти.
- Немає коду швидкого доступу для
mov al, al
(оскільки це в основному дурна річ), тому вам доведеться використовувати mov al, rmb
перевантаження (rmb будучи "зареєструвати або пам'ять"). Це насправді займає три байти. (хоча, ймовірно, mov rb, rmb
замість цього використовується менш специфічний , який має займати лише два байти mov al, al
- аргумент байт використовується для вказівки і джерела, і цільового регістра; тепер ви знаєте, чому 8086 мав лише 8 регістрів: D). Порівняйте NOP
, що є однобайтовою інструкцією! Це економить на пам’яті та часі, оскільки читання пам’яті у 8086 було все-таки досить дорогим, не кажучи вже про завантаження програми з стрічки чи дискети чи що-небудь, звичайно.
То звідки береться xchg ax, ax
? Ви просто повинні подивитися на коди інших xhcg
інструкцій. Ви побачите 0x86
, 0x87
і, нарешті, 0x91
- 0x97
. Так nop
що, 0x90
здається, це дуже добре підходить xchg ax, ax
(що, знову ж таки, не є xchg
«перевантаженням» - вам доведеться використовувати xchg rb, rmb
, у два байти). Насправді я впевнений, що це була приємна побічна дія мікро-архітектури того часу - якщо я пригадую правильно, було легко відобразити весь діапазон 0x90-0x97
на "xchg, діючи над регістрами ax
та ax
- di
" ( операнд симетричний, це дало вам весь діапазон, включаючи ноп xchg ax, ax
; зауважте, що порядок ax, cx, dx, bx, sp, bp, si, di
- bx
це після dx
,ax
; пам'ятайте, імена регістрів - це мнемоніка, а не упорядковані імена - акумулятор, лічильник, дані, база, покажчик стека, базовий покажчик, індекс джерела, індекс призначення). Такий же підхід застосовувався і для інших операндів, наприклад mov someRegister, immediate
набору. Певним чином, ви могли подумати про це так, як ніби опкод насправді не був повноцінним байтом - останні кілька біт є "аргументом" "справжньому" операнду.
Все це, сказане на x86, nop
може вважатися справжньою інструкцією, чи ні. Оригінальна мікро-архітектура вважала це варіантом, xchg
якщо я пам'ятаю правильно, але вона була фактично названа nop
в специфікації. А оскільки xchg ax, ax
насправді це не має сенсу як інструкція, ви можете побачити, як дизайнери 8086 економили на транзисторах і шляхах при розшифровці інструкцій, використовуючи той факт, що 0x90
природно відображає щось, що повністю "безглуздо".
З іншого боку, i8051 має повністю розроблений опкод для nop
- 0x00
. Якийсь практичний. Конструкція інструкцій в основному використовує високий нібл для роботи і низький нибл для вибору операндів - наприклад, add a
є 0x2Y
і 0xX8
означає "зареєструвати 0 прямих", так 0x28
є add a, r0
. Економлять багато на кремнію :)
Я все ще можу продовжувати, оскільки дизайн процесора (не кажучи вже про дизайн компілятора та мовний дизайн) - досить широка тема, але я думаю, що я показав багато різних точок зору, які досить добре вписалися в дизайн.