Інші відповіді розглядають лише NOP, який фактично виконується в якийсь момент - це використовується досить часто, але це не єдине використання NOP.
Невиконання NOP також дуже корисно при написанні коду , який може бути виправлений - в основному, ви будете подушечка функція з кілька НОПОМ після на RET(або аналогічної інструкції). Коли вам доведеться виправити виконувану програму, ви можете легко додати більше коду до функції, починаючи з оригіналу RETта використовуючи стільки тих NOP, скільки вам потрібно (наприклад, для стрибків у довгі або навіть вбудований код) і закінчуючи іншим RET.
У цьому випадку використання ніколи не очікує NOPвиконання. Єдиний момент - дозволити виправлення виконуваного файлу - у теоретичному непідставному виконуваному файлі вам доведеться фактично змінити код самої функції (іноді це може відповідати початковим межам, але досить часто вам знадобиться стрибок у будь-якому випадку ) - це набагато складніше, особливо враховуючи складену вручну збірку або оптимізуючий компілятор; ви повинні поважати стрибки та подібні конструкції, які, можливо, вказували на якийсь важливий фрагмент коду. Загалом, досить хитро.
Звичайно, це було набагато більше застосовувалося в старі часи, коли корисно було робити патчі на зразок цих маленьких і Інтернеті . Сьогодні ви просто розповсюдите перекомпільований двійковий файл і будете робити з ним. Є ще деякі, хто використовує патчі NOP-файлів (виконуючи чи ні, і не завжди буквений NOPs - наприклад, 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. Якщо ви використовували два NOPs, це були б дві інструкції для виконання.
Редагувати:
Власне, обговорення з @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. Економлять багато на кремнію :)
Я все ще можу продовжувати, оскільки дизайн процесора (не кажучи вже про дизайн компілятора та мовний дизайн) - досить широка тема, але я думаю, що я показав багато різних точок зору, які досить добре вписалися в дизайн.