《PC Assembly Language》读书笔记
本書下載地址:pcasm-book。
前言
8086處理器只支持實模式(real mode),不能滿足安全、多任務等需求。
Q:為什么實模式不安全、不支持多任務?為什么虛模式能解決這些問題?
A: 以下是根據網上搜索結果及自己的理解做出的解答,有待斟酌。(1) 安全:實模式下用戶可以訪問任意的物理內存,可以修改系統程序或重要數據的內容,因而不安全。虛模式下用戶能夠訪問的內存是由Descriptor Table中的信息決定的,其基地址是事先不確定的,而長度、權限均有限制,因此相比實模式更安全。(2) 多任務:多任務意味著CPU可以在不同任務之間切換,這要求不同任務之間的地址空間要相互隔離,否則從A任務切換到B任務時,B任務可能會修改A任務使用的內存數據。實模式不支持數據隔離,而虛模式支持。
本書充分使用了兩個開源軟件:NASM匯編器和DJGPP C/C++編譯器。
書中源碼下載地址
第1章 簡介
1.1 數字系統
| word | 2 bytes |
| double word | 4 bytes |
| quad word | 8 bytes |
| paragraph | 16 bytes |
1.2 計算機組成
- AX/BX/CX/DX這四個16-bit的通用寄存器可以分解成兩個8-bit的寄存器
- SI和DI:index registers,通常用于指針,也可用于大多數通用寄存器可以使用的場景,但它們不能分解成8-bit的寄存器。
- BP和SP寄存器用于指向機器語言棧里面的數據。
- CS/DS/SS/ES:段寄存器。CS:Code Segment. DS: Data Segment. SS: Stack Segment. ES: Extra Segment.
- IP寄存器和CS寄存器一起作為指示CPU將要執行的下一條指令的地址。每當執行完一條指令,IP將會指向下一條指令的地址。
- FLAGS寄存器存儲上一條指令的執行結果的重要信息。
- 相比8086,通用寄存器擴展到32-bit。為了后向兼容,AX仍然代表16-bit寄存器,而用EAX來代表擴展的32-bit寄存器。
- 段寄存器仍然是16-bit,此外增加了兩個備用的段寄存器FS和GS。
- 實模式的物理地址計算公式:physical address = 16 * selector + offset
- 為什么不直接存儲物理地址,而將其分解成兩部分?回答:8086的地址需要20-bit的數字來表示,而寄存器只有16-bit,因此需要用兩個16-bit的數值來表示。
- 那為什么不是將20-bit的地址拆分成最高的4-bit和剩下的16-bit,而是用這種有點費解的表示方式? 回答:不知道...
- 實模式的缺點1:一個selector最多只能reference 64KB內存,當代碼大于64KB時,需要分段存儲,在不同段之間跳轉時,CS的值也需要改變。(CS的值存儲在selector中)
- 實模式的缺點2:同一個物理地址對應的段地址不唯一,可以有多種表示方式。
80286處理器使用16-bit保護模式。
- 實模式下selector的值代表物理內存的paragraph數目,保護模式下selector的值代表descriptor table的索引。
- 16-bit 保護模式使用了虛擬內存的技術,其基本思想是:只保留程序正在使用的數據和代碼在內存中,其他數據和代碼臨時存儲在磁盤上。
- descriptor table記錄有每個段的信息:是否在內存中、內存地址、訪問權限等。
- 16-bit 保護模式的一大缺點是offset仍然是16-bit,導致segment大小仍然現在在64KB。
80386處理器使用32-bit保護模式,它與80286使用的16-bit保護模式的主要區別是:
- offset被擴展到32-bits,因此offset的大小增大到40億,從而段大小增大到4GB。
- 段可以劃分成大小為4KB的頁。虛擬內存系統基于頁而非段來工作。
- 每種中斷都分配有一個數字,用于索引中斷向量表,找到對應的中斷handler來處理當前中斷。
- 外部中斷是指由鼠標、鍵盤、定時器等外圍設備觸發的中斷。
- 內部中斷是指有CPU內部觸發的中斷,內部中斷可能來自運行error或中斷指令。Error Interrupt也被稱作trap,由中斷指令觸發的中斷也被稱為軟件中斷。
1.3 匯編語言
- 匯編器是一個將匯編語言轉換為機器語言的程序。
- 匯編語言與高級語言的差異之一:每條匯編語言語句直接代表一條機器指令,而每條高級語言語句可能需要轉換成多條機器指令。
- 匯編語言與高級語言的差異之二:不同類型的CPU使用的匯編語言不相同,而高級語言則可以相同。因此匯編語言的可移植性要低于高級語言。
- 本書使用NASM匯編器(Netwide Assembler),更通用的匯編器是微軟匯編器MASM(Microsoft's Assembler)和Borland匯編器TASM。
操作數有4種類型:
- register
- memory
- immediate
- implied:沒直接表示出來的數,比如自加操作中的1.
匯編語言也有類似C語言的預處理語句,其語句是以%開頭。
- SIZE equ 100
- %define SIZE 100
- L1 db 0: byte labeled L1 with initial value 0
- L2 dw 1000: word labeled L2 with initial value 1000
- L7 resb 1: 1 uninitialized byte labeled L7
- letters for RESX and DX Directives:
- B: byte
- W: word
- D: double word
- Q: quad word
- T: ten bytes
- label可以用來指向代碼中的數據,label相當于指針,在label前后加上中括號([])則表示對指針所指向的數據,類似C語言的星號。
- 作者在asm_io.inc文件中封裝了C語言的I/O函數,包括print_int, print_char, print_string, print_nl, read_int和read_char。
- 疑問:1.3.6節說匯編語言可以使用C標準庫的I/O函數,為什么底層語言可以調用高級語言的函數的?
- 匯編語言文件包含:%include "asm_io.inc"
- 函數調用:使用CALL指令。
1.4 創建程序
- "error: instruction not supported in 64-bit mode"的解決方法:nasm的格式選項改用elf代替elf64,gcc選項增加-m32
- "relocation R_X86_64_32S against '.text' can not be used when making a PIE object; recompile with -fPIC"的解決方法:gcc選項增加-no-pie
- "undefined reference to '_printf'"的解決方法:使用nasm編譯asm_io.asm時,增加-d ELF_TYPE選項。
- 已初始化的數據存儲在.data段
- 未初始化的數據存儲在.bss段
- 代碼存儲在.text段
global:匯編語言的label默認擁有internal scope,這意味著只有同一個模塊的代碼可以使用此label。globel指令使label擁有external scope,使得程序中任意模塊都可使用此label。
windows的目標文件是coff(Common Object File Format)格式的,linux的目標文件是elf(Executable and Linkable Format)格式。
-l listing-file選項可以讓nasm生成一個包含匯編信息的list文件。該文件第2列是數據或代碼在段中的偏移,注意這個偏移值不一定是最終形成整個程序時的真實偏移值,因為不同模塊都可能在數據段定義了自己的label,在鏈接階段,所有數據段的label定義匯總在一個數據段里,這時鏈接器需要重新計算各個label 的偏移。
- IBM框架、大部分RISC處理器和摩托羅拉處理器都是大端序,而Intel處理器是小端序。
- 需要關注字節序的場景:
- 當字節數據在不同主機間傳輸時
- 當字節數據作為多字節整數寫到內存,然后逐個字節讀取時(或者相反)
- 字節序對數組元素的順序不影響,數組的第一個元素永遠在最小的地址。但字節序對數組的每個單獨元素還是會有影響(比如元素是多字節的整數時)。
第2章 匯編語言基礎
整數
- 采用2的補碼的形式來表示負數。2的補碼是指對正數的二進制序列取反碼后加1.阮一峰的一篇日志關于2的補碼解釋了取反碼加1的原因:以-5為例,-5=0-5=100000000 - 00000101=11111111 - 00000101 + 1.
- 當多個數據之間進行運算時,往往需要增大或減少數據的size,這時需要擴展符號位。對unsigned型整數,符號位用0來擴展;對signed型整數,正數用0來擴展符號位,負數用1來擴展符號位。
- MOVZX用于無符號正數擴展,比如將al的值擴展到ax,將ax的值擴展到eax等。
- MOVSX用于有符號正數擴展。
- 數據類型轉換
- CBW: Covert Byte to Word. extends AL to AX
- CWD: Covert Word to Double word. extends AX to DX:AX
- CWDE: Covert Word to Double word Extended. extends AX to EAX
- CDQ: Convert Double word to Quad word. extends EAX to EDX:EAX
- EOF是一個用來表示文件結束的宏(通常被定義為-1),而不是真實存在的字符。
- 加法:add
- 減法:sub
- 無符號乘法:mul source。其中source是寄存器或內存地址,不能是立即數。另一個操作數根據size大小存儲在AL、AX、DX:AX或EDX:EAX中。
- 有符號乘法:
- imul source1
- imul dest, source1
- imul dest, source1, source2
- 無符號除法:div source
- 有符號除法:idiv source
- 取反:NEG
控制結構
- 控制結構根據對數據的比較來決定執行流程,數據比較的結果存儲在FLAGS寄存器中。
- 80x86使用CMP指令來實現比較,其原理是將兩個操作數相減,根據差值設置FLAGS寄存器。
- 無符號整數的比較,需要關注兩個寄存器:ZF(zero)和CF(carry)。
- 有符號整數的比較,需要關注三個寄存器:ZF(zero)、OF(overflow)和SF(sign)。
- 無條件分支:JMP、SHORT、NEAR和FAR。
- 有條件分支:JE、JZ等。
- LOOP: ECX減1,如果ECX不為0,則跳到label
- LOOPE, LOOPZ: ECX減1,如果ECX不為0且ZF=1,則跳到label
- LOOPNE, LOOPNZ: ECX減1,如果ECX不為0且ZF=0,則跳到label
第3章 位操作
移位操作
- 指令:SHL和SHR
- 移動的位數可以是常量或者是存儲在CL寄存器的值
- 移走的那一位的值存儲在CF寄存器中
- 指令:SAL和SAR
- SAL和SHL的行為完全一樣。So,如果左移時改變了符號位怎么辦?
- SAR用符號位來填充最高位。
左移相當于乘以2,右移相當于除以2.
- 指令:ROL和ROR
- 移走的那一位的值存儲在另一端空出來的位上。
布爾位操作
計算整數中1的位數
第4章 子程序
調用者和被調用者必須統一約定好如何傳遞數據。關于如何傳遞數據的規則被稱為calling conventions。
間接尋址
間接尋址允許寄存器像指針變量一樣操作,但要注意寄存器沒有數據類型的概念,寄存器目前是不是作為指針使用、指向什么類型的數據等完全取決于使用的指令。這是匯編語言比高級語言更容易出錯的原因之一。
所有32位的通用寄存器和index寄存器(ESI、EDI)都可用于間接尋址,而16位或8位的寄存器則不能(伍注:我想應該是因為地址是32位的,少于32位的寄存器存不下)
子程序舉例
伍注:call指令背后需要做存儲返回地址等操作,留意一下。
$操作符返回當前行對應的內存地址。
棧
- SS(Stack Segment):存儲棧的段地址
- SP(Stack Pointer):存儲棧的偏移地址,也就是棧頂數據的地址
- BP(Base Pointer): 和SP聯合使用,在尋找棧中的數據和使用個別的尋址方式時會用到。比如說,堆棧中壓入了很多數據或者地址,你肯定想通過SP來訪問這些數據或者地址,但SP是要指向棧頂的,是不能隨便亂改的,這時候你就需要使用BP,把SP的值傳遞給BP,通過BP來尋找堆棧里數據或者地址。
PUSH指令在棧頂插入一個double word(4字節),并導致ESP的值減4;POP指令讀取棧頂的一個double word,并導致ESP的值加4.
棧可以用來臨時存儲數據,以及在進行子程序調用時傳遞參數和局部變量。
80x86可以通過PUSHA指令來將EAX、EBX、ECX、EDX、ESI、EDI和EBP寄存器的值全部壓棧,而通過POPA指令將它們的值全部出棧。
CALL指令會進行無條件跳轉并且將下一條指令的地址壓棧,RET指令會將下一條指令的地址出棧并且跳到那個地址。
調用約定(Calling Conventions)
存儲在棧中的參數在子程序中不會出棧,而是通過指針來訪問。
- C calling Convention: 子程序結束后,由調用者負責清除棧中傳遞給子程序的參數。這樣做的原因是C語言允許函數參數數目可變,這種情況下子程序不能判斷輸入參數的數目,因此由調用者來清除更容易。
- Pascal calling convention: 由子程序負責清除棧中的輸入參數。這樣相對C語言效率更高些,因為清棧的代碼只需在子程序生成一次即可,而不需要每次調用時都生成一遍。Pascal不需要函數參數可變,因此不存在C語言的問題。
- 可重入:每次調用子程序時,會從棧中申請內存來存儲局部變量,子程序結束后會釋放內存。這樣多次調用子程序也不會有影響。(伍注:試想如果使用固定內存來存儲局部變量,多次調用子程序時就會相互影響,從而不能滿足可重入的要求)
- 節省內存:存儲在棧中的數據只會在子程序生命周期中使用內存,不存儲在棧中的數據會在整個程序生命周期中都要使用內存。
多模塊程序
多模塊程序是指有多個目標文件組成的程序。
模塊A如果想訪問模塊B的label1,需要在模塊B中將label1聲明為global,并且在模塊A中將label1定義為extern。
匯編語言與C語言交互
可以在C語言程序中調用匯編子程序。
C語言假定子程序維持以下寄存器的值不變:EBX,ESI,EDI,EBP,CS,DS,SS,ES。具體地說,EBX、ESI和EDI的值必須保持不變,因為C語言使用這些寄存器來存儲register變量。上述其他寄存器在子程序內部可以修改,但在子程序返回前需要恢復原始值。
大部分C編譯器會在函數名、全局變量名或靜態變量名的前面加上一個下劃線,比如DJGPP的gcc編譯器。而在Linux系統中則保持原名、不帶下劃線。
根據C調用約定,函數參數壓棧的順序與它們出現在函數調用的順序相反。這樣有個好處是:對參數數目可變的函數,比如printf函數,格式化字符串在棧中的位置永遠是所有參數的最下面(EBP + 8),這樣子程序通過查看[EBP + 8]的內容可以很方便地判斷出參數數目。
LEA(Load Effective Address)指令用于計算地址。如lea eax, [ebp - 8]語句的作用是把寄存器EBP存儲的內存地址減8后得到的地址賦給寄存器EAX。
根據C調用約定,函數返回值通過寄存器來返回。所有整數類型、指針類型的數據均通過EAX寄存器返回。浮點數通過ST0寄存器返回。
- GCC編譯器運行多種盜用約定。函數的調用約定可以通過__attribute__顯式聲明,比如void f(int) __attribute__((cdecl));
- stdcall和cdecl的區別是前者要求由子程序負責清除棧中的參數,因此前者智能用于參數數目固定的函數。
- GCC提供了regparm屬性用來告訴編譯器用寄存器來傳遞最多3個整數參數,而不是用棧來傳遞。
- Borland和Microsoft為C語言增加了__cdecl和__stdcall關鍵字,這兩個關鍵字作為函數修飾符出現在函數名的前面。比如:void __cdecl f(int);
- cdecl vs stdcall:
- cdecl的優點是簡潔靈活,可以用于任意類型的C函數和C編譯器,缺點主要是相對其他調用約定速度更慢、占用內存更多,因為每次對子函數的調用都需要生成清除棧中參數的代碼。
- stdcall的優點是占用內存更少,缺點主要是不支持函數參數數目可變。
- 使用寄存器來傳遞整數參數的優點是速度更快,缺點主要是復雜,因為當參數較多時,部分參數在寄存器中、部分在棧中。
dump_stack 1, 2, 4解釋:第一個參數“1”是一個數字label,第2、3個參數分別代表打印多少個位于EBP下面和上面的double word。
可重入和遞歸子程序
- 必須不能修改任何代碼指令
- 必須不能修改全局變量(比如存儲在data和bss段的數據),所有變量存儲在棧中。
- 可重入子程序可以被遞歸調用
- 可重入程序可以被多個進程共享
- 可重入子程序在多線程程序中工作得更好(why?)
- global: 在所有函數的外面定義。存儲在data或bss段中,存在于程序的整個生命周期中。默認可以被程序中的任意函數訪問,但如果聲明為static,則只能被同一模塊的程序訪問。
- static: 函數中被聲明為static的局部變量。存儲在data或bss段中。只能在它們所在的函數內被訪問。
- automatic: 函數中定義的局部變量的默認類型。當定義它們的函數被調用時,這些變量會在棧中分配到內存空間,而函數返回時這些內存空間會被釋放。
- register: 該關鍵字請求編譯器用寄存器來存儲當前變量,但編譯器沒必要一定這樣做。C編譯器經常自動將普通的auto變量存儲在寄存器中。但如果變量的地址會在代碼中用到,則不能將其存儲在寄存器中,因為寄存器沒有地址。此外結構體類型也不能定義為register,因為寄存器存不下。
- volatile: 該關鍵字告訴編譯器當前變量的值可能在任意時刻被修改,這意味著編譯器不能對變量的修改時間做任何猜測。此關鍵字可以避免編譯器將變量存儲在寄存器中(編譯器有時會將變量存儲在寄存器中,接著使用寄存器的值來代替變量值),以及避免編譯器自動對代碼中的某些變量賦值進行優化(比如x = 10; y = 20; z =x這段代碼,編譯器往往自動將10復制給z)。
第5章 數組
- 在data段中定義已初始化的數組:使用db, dw, dd等指令,可以使用TIMES指令來重復聲明。
- 在bss段中定義未初始化的數組:使用resb, resw等指令。
- 在stack段中定義局部數組變量:計算出所有局部變量的總字節數,然后將ESP減去此數值。如果總字節數不是4的倍數,需要向上取整到4的倍數,以保證ESP的值以double word為單位。
- C語言根據指針類型來判斷指針運算中需要移動多少個字節,匯編語言中需要程序員來計算在不同元素間跳轉時需要偏移多少個字節。
- 間接尋址公式:[ base_reg + factor * index_reg + constant ]
- CLD: 清除方向標志。此時基址寄存器以遞增的方式工作。(記憶:清真(情增))
- STD: 設置方向標志。此時基址寄存器以遞減的方式工作。
- ESI(Source Index)用于讀內存,EDI(Destination Index)用于寫內存。
- 用于存儲串操作指令的數據的寄存器是固定的,即AL、AX或EAX。
- 串存儲指令使用ES而非DS來標志用于寫內存的段地址,在使用時記得初始化ES的值(在虛模式下ES會自動初始化,在實模式下則不會)。
- 不能通過MOV指令直接將DS寄存器的值復制到ES,而需要使用通用寄存器中轉。
- LODSx: 把由DS:SI指向的源串中的字節(或字)裝入到AL(或AX)中,并根據DF自動修改指針SI,以指向下一個要裝入的字節(或字)。
- STOSx: 把AL(或AX)中的內容存儲到由ES:DI指向的目的串匯總,并根據DF自動修改指針DI,以指向下一個要寫入的字節(或字)。
- MOVSx: 將DS:SI所指向的源串中的一個字節(或字)傳送到ES:DI所指向的存儲單元,并根據DF自動修改SI和DI的值。
- CMPSx: 比較DS:SI所指向的源串中的一個字節(或字)與ES:DI所指向的目的串的一個字節(或字),根據比較結果修改FLAGS寄存器。適用于比較或搜索數組。
- SCASx:比較寄存器AL(或AX)的內容與ES:DI所指向的目的串的一個字節(或字),根據比較結果修改FLAGS寄存器。適用于比較弧搜索數組。
- 指令前綴不是指令,而是位于串指令前面、用來改變指令行為的一個特別的字節。
- REP指令前綴:重復執行某條指令,重復次數存儲在ECX寄存器中。
- REPx指令前綴:重復執行某條指令,直到某個條件不再滿足或重復次數達到ECX的值。注意:當重復操作由于比較結果不再滿足要求而中止時,基址寄存器仍然會加1、ECX寄存器仍然會減1,而FLAGS寄存器仍然保持中止時的狀態。因此,可以通過Z標志來判斷操作中止是因為比較結果不滿足要求還是ECX變為0(ECX變為0只能說明達到最大重復次數,但不能確認最后一次的比較結果,所以還是需要看FLAGS寄存器)。
第6章 浮點數
浮點數的表示
十進制小數轉換成二進制小數:不斷乘以2,取個位。
IEEE定義了兩種精度不同的浮點數表示法:單精度(float)和雙精度(double)。Intel的數學協處理器還使用了第三種精度更高的表示法,叫做extended precision。
注意某個數值在A進制下是有限小數,在B進制下卻可能是無限循環小數。比如1/3在十進制下是無限循環小數0.3333···,在三進制下則是0.1.
- s: 最高位是符號位,0為整數,1為負數
- e: 第30~23位是偏移后的指數,其數值為正確的指數值加上127.指數為0或0xFF時有特殊含義。
- f: 第22~0位是底數,其數值為緊接在個位的1后面的23位。
- 單精度的整數大小范圍是1.0*2^(-126) ~ 1.1111...*2^127,對應十進制的1.1755*10^(-35) ~ 3.4028*10^35.
- e = 0 and f = 0:表示0,注意存在+0和-0(視最高位的符號位而定)
- e = 0 and f != 0:表示非規范化的小數,用于數值非常小的小數。
- e = FF and f = 0: 表示無限大(符號位為正)或無限小(符號位為負)
- e = FF and f != 0: 表示不確定的結果,NaN(Not a Number)。比如計算負數的平方根、將兩個無限的數相加時會返回NaN。
- 指數的位數增加到11位,指數偏移量由127改為1023.
- 底數的位數增加到52位
- 單精度的整數大小范圍為10^(-308)至10^(308).
浮點數的四則運算
由于計算機的位數有限,許多浮點數不能被精確表示。
數字協處理器
Intel提供一個額外芯片(數學協處理器)來支持浮點數運算,8086、80286、80386處理器對應的協處理器分別是8087、80287、80387。自Pentium以后數學協處理器就做進CPU里面了。
- 數字協處理器有8個浮點寄存器,名字分別是ST0,ST1,...ST7。每個寄存器能存儲80位的數據,并且是按照棧的LIFO方式來管理數據的。
- ST0永遠存儲棧頂數據的地址。
- 數字協處理器也有1個狀態寄存器。
- 為區分正常CPU指令,數字協處理器的指令均以F開頭。
- 加載和保存指令
- FLD source: 從內存中加載一個浮點數到棧頂,source可以是立即數或協處理器寄存器。
- FILD source: 從內存中加載一個整數,將其轉化為浮點數并保存到棧頂。source是立即數。
- FLD1: 保存數字1到棧頂
- FLDZ: 保存數字0到棧頂
- FST dest: 保存棧頂到內存。dest是立即數或協處理器寄存器。
- FSTP dest: 保存棧頂到內存,并將棧頂的數字出棧。
- FIST dest: 將棧頂數字轉換成整數,并保存到內存中。dest是單字或雙字。
FISTP:除了棧頂數字出棧和dest可以是四字外,與FIST相同。
- FXCH STn: 交換ST0和STn在棧中的數值
- FFREE STn: 釋放棧中的一個寄存器(即將該寄存器標識為unused或empty)
- 加減法指令
- FADD src: ST0 += src。src可以是協處理器寄存器或立即數。
- FADD dest, ST0: dest += ST0. dest是協處理器寄存器。
- FADDP dest或FADDP dest ST0: dest += ST0,然后出棧。
- FIADD src: ST0 += (float) src. src是立即數(整數)。
- FSUB src: ST0 -= src. src可以是協處理器寄存器或立即數。
- FSUBR src: ST0 = src - ST0. src同上。
- FSUB dest, ST0: dest -= ST0. dest是協處理器寄存器。
- FSUBR dest, ST0: dest = ST0 - dest.
- FSUBP dest或FSUBP dest, ST0: dest -= ST0,然后出棧。
- FSUBRP dest或FSUBRP dest, ST0: dest = ST0 - dest,然后出棧。
- FISUB src: ST0 -= (float) src.
- FISUBR src: ST0 = (float) src - ST0.
- 乘除法指令
- FMUL src: ST0 *= src.
- FMUL dest, ST0: dest *= ST0.
- FMULP dest或FMULP dest, ST0: dest *= ST0,然后出棧。
- FIMUL src: ST0 *= (float) src.
- FDIV src: ST0 /= src.
- FDIVR src: ST0 = src / ST0.
- FDIV dest, ST0: dest /= ST0.
- FDIVR dest, ST0: dest = ST0 / dest.
- FDIVP dest或FDIVP dest, ST0: dest /= ST0,然后出棧。
- FDIVRP dest或FDIVRP dest, ST0: dest = ST0 / dest,然后出棧。
- FIDIV src: ST0 /= (float)src.
- FIDIVR src: ST0 = (float)src / ST0.
- 比較指令
- FCOM src: 比較ST0和src。
- FCOMP src: 比較ST0和src,然后出棧。
- FCOMPP: 比較ST0和ST1,然后出棧兩次。
- FICOM src: 比較ST0和(float)src。
- FICOMP src: 比較ST0和(float)src,然后出棧。
- FTST: 比較ST0和0.
- 宏指令
- FCHS: ST0 = - ST0
- FABS: ST0 = |ST0|
- FSORT: ST0 = sqrt(ST0)
- FSCALE: ST0 = ST0 * 2^[ST1].
第7章 結構體與C++
匯編與C++
- C++允許多個函數名字相同,只要它們的參數類型不完全相同即可。注意:C++不允許兩個函數名字和參數類型完全相同而只有返回值類型不同。
- C++使用“函數名字+函數參數類型縮寫”的方式來修飾函數名。(注意名字修飾時沒用到返回值類型,這也是為啥C++ 不允許兩個函數只有返回值類型不同的原因。)
- C++對所有函數都添加函數參數類型信息來加以修飾,因為它無法判斷特定函數是否被重載。
- C++對全局變量也都添加函數參數類型信息來加以修飾。
- 類型安全鏈接(typesafe linking):函數或全局變量在不同地方的類型不一致。
- 由于不同編譯器使用不同的名字修飾規則,不同編譯器編譯的C++代碼可能無法鏈接到一起。在使用已經編譯好的C++庫時需要注意其使用的編譯器的名字修飾規則。
- 由于C和C++的名字修飾規則不同,在C++中調用C函數時可能會鏈接失敗,為解決該問題,可以使用extern "C"語句來告訴編譯器對應的函數或全局變量使用傳統C的編譯鏈接方式。
引用:C++相對C語言引入的新特性。它允許程序員無需使用指針就能傳遞參數地址給函數。
內聯函數:不會發生函數調用,而是將函數體的代碼替換到調用處。內聯函數的主要缺點是內聯代碼不會鏈接,因此所有需要使用該內聯函數的文件都必須能訪問到該內聯函數所在的文件;而且,當修改了內聯函數的實現,所有調用該函數的文件需要重新編譯。
轉載于:https://www.cnblogs.com/wuhualong/p/read_pc_assembly_language.html
總結
以上是生活随笔為你收集整理的《PC Assembly Language》读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海豚调度器初次使用 .......
- 下一篇: 吐槽 树洞