คำตอบอื่น ๆ เพียงแค่พิจารณา NOP ที่ดำเนินการจริงในบางจุด - ที่ใช้กันทั่วไป แต่ไม่ใช่การใช้ NOP เพียงอย่างเดียว
ไม่ใช่การดำเนิน NOP ยังเป็นประโยชน์สวยเมื่อเขียนโค้ดที่สามารถปะ - พื้นคุณจะแผ่นฟังก์ชั่นที่มีไม่กี่ nops หลังจากRET
(หรือคำสั่งที่คล้ายกัน) เมื่อคุณมีการแก้ไขการปฏิบัติการคุณสามารถเพิ่มรหัสเพิ่มเติมฟังก์ชั่นเริ่มต้นจากเดิมRET
และการใช้เป็นจำนวนมากของผู้ nops ตามที่คุณต้องการ (เช่นนานกระโดดหรือแม้แต่รหัสแบบอินไลน์) RET
และการตกแต่งอีกด้วย
ในกรณีที่ใช้งานนี้noöneคาดว่าNOP
จะดำเนินการ จุดเดียวคือการอนุญาตให้ทำการปะไฟล์ปฏิบัติการได้ - ในปฏิบัติการแบบไม่อัดในเชิงทฤษฎีคุณต้องเปลี่ยนรหัสของฟังก์ชันเอง (บางครั้งมันอาจจะพอดีกับขอบเขตดั้งเดิม แต่บ่อยครั้งที่คุณต้องกระโดดต่อไป ) - มีความซับซ้อนมากขึ้นโดยเฉพาะการพิจารณาชุดประกอบที่เขียนด้วยตนเองหรือคอมไพเลอร์ที่ปรับให้เหมาะสม คุณต้องเคารพการกระโดดและการสร้างที่คล้ายกันซึ่งอาจชี้ไปที่ชิ้นส่วนสำคัญของรหัส สรุปแล้วค่อนข้างยุ่งยาก
ของหลักสูตรนี้เป็นจำนวนมากใช้มากขึ้นอย่างมากในสมัยก่อนเมื่อมันเป็นประโยชน์เพื่อให้แพทช์เช่นนี้มีขนาดเล็กและออนไลน์ วันนี้คุณเพียงแค่แจกจ่ายไบนารีที่คอมไพล์แล้วและทำได้ ยังคงมีบางคนที่ใช้ NOP สำหรับการปะแก้ (ดำเนินการหรือไม่และไม่ได้เป็นตัวอักษรเสมอไปNOP
- ตัวอย่างเช่น Windows ใช้MOV EDI, EDI
สำหรับการปรับปรุงออนไลน์ - ซึ่งเป็นประเภทที่คุณสามารถอัปเดตไลบรารีระบบในขณะที่ระบบกำลังทำงานจริง
ดังนั้นคำถามสุดท้ายคือทำไมมีคำสั่งเฉพาะสำหรับสิ่งที่ไม่ได้ทำอะไรจริงๆ?
- มันเป็นคำสั่งที่เกิดขึ้นจริง - สำคัญเมื่อทำการดีบักหรือประกอบ handcoding คำแนะนำเช่น
MOV AX, AX
จะทำสิ่งเดียวกัน แต่ไม่ส่งสัญญาณความตั้งใจค่อนข้างชัดเจน
- แพ็ดดิ้ง - "รหัส" ที่มีเพียงเพื่อปรับปรุงประสิทธิภาพโดยรวมของรหัสที่ขึ้นอยู่กับการจัดตำแหน่ง มันไม่ได้หมายถึงการดำเนินการ ผู้ debuggers บางคนเพียงซ่อน padding NOPs ในการถอดแยกชิ้นส่วนของพวกเขา
- มันให้พื้นที่เพิ่มเติมสำหรับการปรับแต่งคอมไพเลอร์ - รูปแบบที่ยังใช้อยู่คือคุณมีสองขั้นตอนในการคอมไพล์อันแรกนั้นค่อนข้างง่ายและสร้างรหัสแอสเซมบลีที่ไม่จำเป็นจำนวนมากในขณะที่อีกอันหนึ่ง คำแนะนำที่ไม่เกี่ยวข้อง สิ่งนี้มักจะเห็นในภาษาที่คอมไพล์ด้วย JIT ด้วยเช่นกัน - ทั้งโค้ด IL และ JVM ของ. NET ใช้โค้ดไบต์
NOP
ค่อนข้างมาก รหัสแอสเซมบลีที่คอมไพล์จริงไม่มีอีกต่อไป ควรสังเกตว่ามันไม่ใช่ x86- NOP
s
- มันทำให้การดีบักออนไลน์ง่ายขึ้นทั้งสำหรับการอ่าน (หน่วยความจำที่เป็นศูนย์ล่วงหน้าจะ
NOP
ทำให้การแยกส่วนง่ายต่อการอ่านมากขึ้น) และสำหรับการปะแก้ร้อน (แม้ว่าฉันจะชอบแก้ไขและดำเนินการต่อใน Visual Studio: P)
สำหรับการดำเนินการ NOP มีจุดอีกสองสามอย่าง:
- ประสิทธิภาพแน่นอน - นี่ไม่ใช่สาเหตุที่มันเกิดขึ้นในปี 8085 แต่แม้แต่ 80486 ก็มีการดำเนินการเรียนการสอนแบบไปป์ไลน์ซึ่งทำให้ "ไม่ต้องทำอะไรเลย" ค่อนข้างยุ่งยาก
- เท่าที่เห็นด้วย
MOV EDI, EDI
มี nops ที่มีประสิทธิภาพอื่น ๆ NOP
กว่าที่แท้จริง MOV EDI, EDI
มีประสิทธิภาพที่ดีที่สุดในรูปแบบ 2 ไบต์ NOP ใน x86 หากคุณใช้สองNOP
s นั่นจะเป็นสองคำสั่งในการดำเนินการ
แก้ไข:
ที่จริงแล้วการสนทนากับ @DmitryGrigoryev บังคับให้ฉันคิดเกี่ยวกับสิ่งนี้อีกเล็กน้อยและฉันคิดว่ามันเป็นส่วนเพิ่มเติมที่มีค่าสำหรับคำถาม / คำตอบนี้ดังนั้นให้ฉันเพิ่มบิตพิเศษบางอย่าง:
ครั้งแรกจุดที่เห็นได้ชัด - ทำไมจะมีการเรียนการสอนที่ไม่เหมือนบางสิ่งบางอย่างmov ax, ax
? ตัวอย่างเช่นลองดูที่กรณีของรหัสเครื่อง 8086 (เก่ากว่าแม้แต่รหัสเครื่อง 386):
- มีการเรียนการสอน NOP ทุ่มเทกับ
0x90
opcode ยังคงเป็นเวลาที่หลายคนเขียนไว้ในใจคุณ ดังนั้นแม้ว่าจะไม่มีNOP
คำสั่งเฉพาะคำNOP
หลัก (นามแฝง / ตัวย่อ) จะยังคงมีประโยชน์และจะแมปกับสิ่งนั้น
- คำแนะนำเช่น
MOV
แมปจริงกับ opcodes ที่แตกต่างกันมากมายเนื่องจากช่วยประหยัดเวลาและพื้นที่ตัวอย่างเช่นmov al, 42
"ย้ายไบต์ทันทีไปยังการal
ลงทะเบียน" ซึ่งแปลว่า0xB02A
( 0xB0
เป็น opcode ซึ่ง0x2A
เป็นอาร์กิวเมนต์ "ทันที") ดังนั้นมันใช้เวลาสองไบต์
- ไม่มี opcode ทางลัดสำหรับ
mov al, al
(เนื่องจากเป็นสิ่งที่ต้องทำโดยทั่วไป) ดังนั้นคุณจะต้องใช้mov al, rmb
(rmb เป็น "การลงทะเบียนหรือหน่วยความจำ") เกินพิกัด จริง ๆ แล้วใช้เวลาสามไบต์ (แม้ว่ามันอาจจะใช้ค่าที่น้อยกว่าmov rb, rmb
แทนซึ่งควรใช้สองไบต์เท่านั้นmov al, al
- ไบต์ของอาร์กิวเมนต์ถูกใช้เพื่อระบุทั้งแหล่งที่มาและรีจิสเตอร์เป้าหมายตอนนี้คุณรู้แล้วว่าทำไม 8086 มีเพียง 8 รีจิสเตอร์: D) เปรียบเทียบกับNOP
ซึ่งเป็นคำสั่งไบต์เดียว! สิ่งนี้ช่วยประหยัดหน่วยความจำและเวลาเนื่องจากการอ่านหน่วยความจำใน 8086 ยังคงมีราคาค่อนข้างสูง - ไม่ต้องพูดถึงการโหลดโปรแกรมนั้นจากเทปหรือฟลอปปี้หรือบางสิ่งบางอย่าง
แล้วxchg ax, ax
มาจากไหน? คุณเพียงแค่ต้องดู opcodes ของxhcg
คำแนะนำอื่น ๆ คุณจะเห็น0x86
, 0x87
และสุดท้าย-0x91
0x97
ดังนั้นnop
กับมัน0x90
ดูเหมือนว่าเป็นแบบที่ดีงามสำหรับxchg ax, ax
(ซึ่งอีกครั้งไม่ได้เป็นxchg
"เกิน" - คุณจะต้องใช้xchg rb, rmb
เวลาสอง bytes) และในความเป็นจริงฉันค่อนข้างแน่ใจว่านี่เป็นผลข้างเคียงที่ดีของสถาปัตยกรรมไมโครของเวลา - ถ้าฉันจำได้อย่างถูกต้องมันเป็นเรื่องง่ายที่จะแมปช่วงทั้งหมดของ0x90-0x97
"xchg, แทนการลงทะเบียนax
และax
- di
" ( ถูกดำเนินการเป็นสมมาตรนี้ให้คุณครบวงจรรวมทั้ง nop xchg ax, ax
; ทราบว่าสั่งซื้อax, cx, dx, bx, sp, bp, si, di
- bx
หลังdx
,ax
; จำไว้ว่าชื่อการลงทะเบียนเป็นตัวช่วยจำชื่อที่ไม่ได้เรียงลำดับ - ตัวสะสม, ตัวนับ, ข้อมูล, ฐาน, ตัวชี้สแต็ก, ตัวชี้ฐาน, ดัชนีต้นทาง, ดัชนีปลายทาง) วิธีการเดียวกันนี้ยังใช้สำหรับตัวถูกดำเนินการอื่นเช่นmov someRegister, immediate
ชุด ในทางใดทางหนึ่งคุณอาจคิดว่าสิ่งนี้ราวกับว่า opcode ไม่ใช่ไบต์เต็ม - จริง ๆ แล้วบิตสุดท้ายคือ "อาร์กิวเมนต์" สำหรับตัวถูกดำเนินการ "ของจริง"
ทั้งหมดนี้กล่าวว่าใน x86 nop
อาจได้รับการพิจารณาว่าเป็นคำสั่งจริงหรือไม่ สถาปัตยกรรมไมโครดั้งเดิมนั้นถือว่าเป็นตัวแปรxchg
ถ้าฉันจำได้ถูกต้อง แต่จริงๆแล้วมันถูกตั้งชื่อnop
ในสเปค และเนื่องจากxchg ax, ax
ไม่มีความเหมาะสมในการเรียนการสอนคุณสามารถเห็นได้ว่านักออกแบบของ 8086 ที่บันทึกไว้ในทรานซิสเตอร์และเส้นทางในการถอดรหัสคำสั่งโดยใช้ประโยชน์จากความจริงที่ว่า0x90
แผนที่แมปกับสิ่งที่ "noppy" โดยธรรมชาติ
บนมืออื่น ๆ , i8051 ได้รับการออกแบบในทั้งหมด opcode สำหรับ-nop
0x00
ค่อนข้างเป็นประโยชน์ การออกแบบการเรียนการสอนเป็นพื้นใช้ตอดสูงสำหรับการดำเนินงานและต่ำตอดสำหรับการเลือกตัวถูกดำเนินการ - สำหรับตัวอย่างเช่นadd a
เป็น0x2Y
และ0xX8
วิธีการที่ "ลงทะเบียน 0 โดยตรง" เพื่อให้เป็น0x28
add a, r0
ช่วยประหยัดซิลิคอนได้มาก :)
ฉันยังคงดำเนินต่อไปเนื่องจากการออกแบบ CPU (ไม่พูดถึงการออกแบบคอมไพเลอร์และการออกแบบภาษา) เป็นหัวข้อที่ค่อนข้างกว้าง แต่ฉันคิดว่าฉันได้แสดงมุมมองที่แตกต่างมากมายที่เข้ามาในการออกแบบค่อนข้างดี