Các câu trả lời khác chỉ đang xem xét một NOP thực sự thực thi tại một số điểm - được sử dụng khá phổ biến, nhưng đó không phải là cách sử dụng NOP duy nhất.
Các NOP không thực hiện cũng khá hữu ích khi viết mã có thể được vá - về cơ bản, bạn sẽ pad chức năng với một vài NOPs sau sự RET
(hoặc hướng dẫn tương tự). Khi bạn phải vá tệp thực thi, bạn có thể dễ dàng thêm nhiều mã hơn vào hàm bắt đầu từ bản gốc RET
và sử dụng bao nhiêu NOP mà bạn cần (ví dụ: để nhảy dài hoặc thậm chí mã nội tuyến) và hoàn thiện bằng mã khác RET
.
Trong trường hợp sử dụng này, noöne từng mong đợi NOP
thực thi. Điểm duy nhất là cho phép vá lỗi thực thi - trong một thực thi không đệm về mặt lý thuyết, bạn phải thực sự thay đổi mã của hàm (đôi khi nó có thể phù hợp với các ranh giới ban đầu, nhưng thường thì bạn sẽ cần một bước nhảy ) - điều đó phức tạp hơn nhiều, đặc biệt là xem xét lắp ráp bằng tay hoặc trình biên dịch tối ưu hóa; bạn phải tôn trọng các bước nhảy và các cấu trúc tương tự có thể đã chỉ ra một số đoạn mã quan trọng. Tất cả trong tất cả, khá khó khăn.
Tất nhiên, điều này đã được sử dụng nhiều hơn rất nhiều vào thời xa xưa, khi nó rất hữu ích để tạo ra các bản vá như nhỏ và trực tuyến . Hôm nay, bạn sẽ chỉ phân phối một nhị phân được biên dịch lại và được thực hiện với nó. Vẫn còn một số người sử dụng các bản vá NOP (thực thi hay không, và không phải lúc nào cũng bằng chữ NOP
- ví dụ, Windows sử dụng MOV EDI, EDI
để vá trực tuyến - đó là loại mà bạn có thể cập nhật thư viện hệ thống trong khi hệ thống thực sự đang chạy mà không cần khởi động lại).
Vì vậy, câu hỏi cuối cùng là, tại sao có một hướng dẫn chuyên dụng cho một cái gì đó không thực sự làm gì?
- Đây là một hướng dẫn thực tế - quan trọng khi gỡ lỗi hoặc lắp ráp mã hóa. Các hướng dẫn như
MOV AX, AX
sẽ thực hiện chính xác như vậy, nhưng không báo hiệu ý định khá rõ ràng.
- Đệm - "mã" đó chỉ để cải thiện hiệu suất tổng thể của mã phụ thuộc vào căn chỉnh. Nó không bao giờ có nghĩa là để thực thi. Một số trình gỡ lỗi chỉ đơn giản là ẩn NOP đệm trong quá trình tháo gỡ của chúng.
- Nó cung cấp thêm không gian để tối ưu hóa trình biên dịch - mẫu vẫn được sử dụng là bạn đã có hai bước biên dịch, bước đầu tiên khá đơn giản và tạo ra nhiều mã lắp ráp không cần thiết, trong khi cách thứ hai làm sạch, tua lại các tham chiếu địa chỉ và xóa hướng dẫn ngoại lai. Điều này cũng thường thấy trong các ngôn ngữ do JIT biên dịch - cả mã byte của IL và JVM của .NET đều sử dụng
NOP
khá nhiều; mã lắp ráp được biên dịch thực tế không còn những cái đó nữa. Tuy nhiên, cần lưu ý rằng những cái đó không phải là x86 NOP
.
- Nó giúp việc gỡ lỗi trực tuyến dễ dàng hơn cho cả việc đọc (bộ nhớ trước sẽ là tất cả
NOP
, giúp cho việc phân tách dễ đọc hơn rất nhiều) và để vá nóng (mặc dù tôi rất thích Chỉnh sửa và Tiếp tục trong Visual Studio: P).
Để thực hiện NOP, tất nhiên còn một vài điểm nữa:
- Hiệu suất, tất nhiên - đây không phải là lý do tại sao nó là vào năm 8085, nhưng ngay cả 80486 cũng đã có một lệnh thực thi theo đường ống dẫn đến việc "không làm gì" một chút khó khăn hơn.
- Như đã thấy
MOV EDI, EDI
, có những NOP hiệu quả khác ngoài nghĩa đen NOP
. MOV EDI, EDI
có hiệu suất tốt nhất là NOP 2 byte trên x86. Nếu bạn đã sử dụng hai NOP
s, đó sẽ là hai hướng dẫn để thực thi.
CHỈNH SỬA:
Trên thực tế, cuộc thảo luận với @DmitryGrigoryev buộc tôi phải suy nghĩ về vấn đề này nhiều hơn một chút và tôi nghĩ đó là một bổ sung có giá trị cho câu hỏi / câu trả lời này, vì vậy hãy để tôi thêm một số bit:
Đầu tiên, điểm, rõ ràng - tại sao sẽ có một hướng dẫn làm một cái gì đó như thế mov ax, ax
nào? Ví dụ: chúng ta hãy xem trường hợp mã máy 8086 (cũ hơn mã máy thậm chí là 386):
- Có một hướng dẫn NOP chuyên dụng với opcode
0x90
. Đây vẫn là thời gian mà nhiều người viết trong hội đồng, nhớ bạn. Vì vậy, ngay cả khi không có NOP
hướng dẫn chuyên dụng , NOP
từ khóa (bí danh / ghi nhớ) vẫn sẽ hữu ích và sẽ ánh xạ tới đó.
- Các hướng dẫn như
MOV
thực sự ánh xạ tới nhiều opcode khác nhau, bởi vì điều đó giúp tiết kiệm thời gian và không gian - ví dụ, mov al, 42
là "di chuyển byte ngay lập tức đến thanh al
ghi", nghĩa là 0xB02A
( 0xB0
là opcode, 0x2A
là đối số "ngay lập tức"). Vì vậy, cần hai byte.
mov al, al
Về cơ bản, không có opcode cho (vì đó là một việc ngu ngốc), vì vậy bạn sẽ phải sử dụng quá tải mov al, rmb
(rmb là "đăng ký hoặc bộ nhớ"). Điều đó thực sự mất ba byte. (mặc dù nó có thể sẽ sử dụng ít đặc hiệu hơn mov rb, rmb
thay vào đó, chỉ nên lấy hai byte cho mov al, al
- byte đối số được sử dụng để chỉ định cả nguồn và thanh ghi đích; bây giờ bạn biết tại sao 8086 chỉ có 8 thanh ghi: D). So sánh với NOP
, đó là một lệnh byte đơn! Điều này giúp tiết kiệm bộ nhớ và thời gian, vì đọc bộ nhớ trong 8086 vẫn còn khá tốn kém - chưa kể đến việc tải chương trình đó từ băng hoặc đĩa mềm hoặc một cái gì đó, tất nhiên.
Vậy xchg ax, ax
nguồn gốc từ đâu? Bạn chỉ cần nhìn vào opcodes của các xhcg
hướng dẫn khác . Bạn sẽ thấy 0x86
, 0x87
và cuối cùng, 0x91
- 0x97
. Vì vậy, nop
có 0x90
vẻ như nó khá phù hợp với xchg ax, ax
(một lần nữa, không phải là xchg
"quá tải" - bạn phải sử dụng xchg rb, rmb
, ở hai byte). Và trên thực tế, tôi khá chắc chắn rằng đây là một hiệu ứng phụ tuyệt vời của kiến trúc vi mô thời bấy giờ - nếu tôi nhớ lại một cách chính xác, thật dễ dàng để ánh xạ toàn bộ phạm vi của 0x90-0x97
"xchg, hành động qua các thanh ghi ax
và ax
- di
" ( toán hạng là đối xứng, điều này cung cấp cho bạn phạm vi đầy đủ, bao gồm cả nop xchg ax, ax
, lưu ý rằng thứ tự là ax, cx, dx, bx, sp, bp, si, di
- bx
là saudx
,ax
; hãy nhớ rằng, tên đăng ký là ghi nhớ, không được đặt hàng tên - bộ tích lũy, bộ đếm, dữ liệu, cơ sở, con trỏ ngăn xếp, con trỏ cơ sở, chỉ mục nguồn, chỉ mục đích). Cách tiếp cận tương tự cũng được sử dụng cho các toán hạng khác, ví dụ như mov someRegister, immediate
tập hợp. Theo một cách nào đó, bạn có thể nghĩ về điều này như thể opcode thực sự không phải là một byte đầy đủ - một vài bit cuối cùng là "một đối số" cho toán hạng "thực".
Tất cả điều này đã nói, trên x86, nop
có thể được coi là một hướng dẫn thực sự hoặc không. Kiến trúc vi mô ban đầu đã coi nó như một biến thể xchg
nếu tôi nhớ chính xác, nhưng nó thực sự được đặt tên nop
trong đặc tả. Và vì xchg ax, ax
không thực sự có ý nghĩa như một hướng dẫn, bạn có thể thấy các nhà thiết kế của 8086 đã lưu trên các bóng bán dẫn và đường dẫn trong việc giải mã hướng dẫn bằng cách khai thác thực tế rằng 0x90
ánh xạ tự nhiên vào một thứ hoàn toàn "mềm mại".
Mặt khác, i8051 có opcode được thiết kế hoàn toàn cho nop
- 0x00
. Thực tế. Việc thiết kế hướng dẫn về cơ bản là sử dụng nibble cao đối với hoạt động và nibble thấp để lựa chọn các toán hạng - ví dụ, add a
là 0x2Y
, và 0xX8
có nghĩa là "đăng ký 0 trực tiếp", do đó 0x28
là add a, r0
. Tiết kiệm rất nhiều trên silicon :)
Tôi vẫn có thể tiếp tục, vì thiết kế CPU (không đề cập đến thiết kế trình biên dịch và thiết kế ngôn ngữ) là một chủ đề khá rộng, nhưng tôi nghĩ rằng tôi đã thể hiện nhiều quan điểm khác nhau đi vào thiết kế khá độc đáo.