第三章
程序的機器級表示
content
3.2程序編碼
linux> gcc -Og -o p p1.c p2.c
編譯選項 -Og 告訴編譯器使用會生成符合原始C代碼整體結構的機器代碼的優化等級。(太高的優化等級會使代碼嚴重變形,與源代碼關係不易理解)
3.2.1 機器級代碼
計算機系統使用了多種不同形式的抽象,利用更簡單的抽象模型來隱藏實現的細節。對於機器級編程來說,有兩種抽象尤爲重要。第一種:指令集體系結構或指令集架構(Instruction Set Architecture,ISA)來定義機器級程序格式和行爲,它定義了處理器狀態、指令的格式,以及每條指令對狀態的影響。第二種:機器級程序使用的內存地址是虛擬地址,提供的內存模型看上去是一個非常大的字節數組。
1) 程序計數器(通常稱爲“PC”,在 x86-64 中用 %rip 表示)給出將要執行的下一條指令內存中的地址。
2) 整數寄存器文件包含16個命名的位置,分別存儲64位的值。
3) 條件碼寄存器保存着最近執行的算術或者邏輯指令的狀態信息。
4) 一組向量寄存器可以存放一個或多個整數或浮點數值。
程序內存包含:程序的可執行機器代碼,操作系統需要的一些信息,用來管理過程調用和返回的運行時棧,以及用戶分配的內存塊(比如說用 malloc 庫函數分配的)。
一條機器指令只執行一個非常基本的操作。
3.2.2 代碼示例
寫一個C代碼文件 mstore.c , 包含如下的函數定義:
long mult2(long,long);
void multstore(long x,long y,long*dest){
long t = mult2(x,y);
*dest = t;
}
在命令行上使用“-S”選項,就能看到編譯器產生的彙編代碼,查看 mstroe.s 即可。
Linux> gcc -Og -S mstore.c
Linux> cat ./mstore.s
內容如下:
其中有一段代碼是這樣的:
multstore:
pushq %rbx
movq %rdx, %rbx
call mult2
movq %rax, (%rbx)
popq %rbx
ret
以上從 multstore 之後開始的每一條代碼都對應一條機器指令。比如, pushq 表示將寄存器 %rbx 的內容壓入棧。
可以使用”-C”選項對其編譯並彙編,如:
Linux> gcc -Og -c mstore.c
Linux>objdump -d mstore.o
機器代碼就是左側的數字
53 48 89 d3 e8 00 00 00 00 48 89 03 5b c3
右邊就是等價的彙編語言。
一些機器代碼和它的反彙編表示的特性:
1) x86-64 的指令長度從 1 到 15 個字節不等。
2) 設計指令格式的方式是,從某個給定位置開始,可以將字節唯一地解碼成機器指令。
3) 反彙編器指數基於機器代碼文件中的字節序列來確定彙編代碼。它不需要訪問該程序的源代碼或彙編代碼。
4) 反彙編其使用的指令命名規則與GCC生成的彙編代碼使用的有些細微的差別。
# include <stdio.h>
void multstore(long , long , long * );
int main(){
long d;
multstore(2 , 3 , &d);
printf("2 * 3 --> %ld/n" , d);
return 0; } long mult2(long a , long b){ long s = a * b ;
return s ; }
同樣的方法生成機器代碼
Linux>gcc -Og -o prog main.c mstore.c
Linux>objdump -d prog
反彙編器會抽取出各種代碼序列,其中包含下面這段:
幾乎與前面的 mstroe.c 產生的代碼一樣。
3.2.3 關於格式的註釋
GCC 產生的彙編代碼有點難讀。所有以 “ . ” 開頭的焊都是指導彙編器和鏈接器工作的僞指令。(可以忽略)
帶註釋的示例:
兩種方法在C語言中插入彙編代碼。
一種是用匯編代碼編寫整個函數,在連接階段把它們和C函數組合起來。
二是利用GCC的支持,直接在C程序中嵌入彙編代碼。
具體爲:
3.3 數據格式
3.4 訪問信息
一個 x86-64 的中央處理器(CPU)包含一組16個存儲64位值的通用目的寄存器。這些寄存器;用來存儲整數數據和指針。
對於生成1字節和2字節數字的指令會保持剩下的字節不變;生成4字節數字的指令會把高位4個字節置爲0。
3.4.1 操作數指示符
大多數指令有一個或多個操作數(operand),指示出執行一個操作中要使用的源數據值,以及放置結果的目的位置。各種不同的操作數的可能性被分爲三種類型。第一種是立即數(immediate);第二種是寄存器(register);第三種是內存引用。
有多種不同的尋址模式。
3.4.2 數據傳送指令
使用最頻繁的指令是將數據從一個位置複製到另一個位置的指令。
最簡單的數據傳送指令——MOV類。
MOV指令示例:
移動填充指令如下:
擴展示例:
3.4.3 數據傳送示例
3.4.4 壓入和彈出棧數據
將一個四字值壓入棧中,首先要將棧指針減8,然後將值寫到新的棧頂地址。例如:
pushq %rbp
等價於
Decrement stack pointer
subq $8, %rsp
Store %rbp on stack
movq %rbp, (%rsp)
具體過程如下:
3.5 算術和邏輯操作
3.5.1 加載有效地址
加載有效地址(load effective address)指令 leaq 實際上是 movq 指令的變形。它的指令形式是從內存讀數據到寄存器,但實際上它根本就沒有引用內存,而是將有效地址寫入到目的操作數。
3.5.2 一元和二元操作
3.5.3 移位操作
左移指令有兩個名字: SAL 和 SHL(二者效果一樣,都是將右邊補上 0 )。右移指令也有兩個: SAR 執行算術右移(左邊填上符號位), 而 SHR執行邏輯移位(填上 0 )。
3.5.4 討論
大多數指令既可以用於無符號運算,也可以用於補碼運算。只有右移操作要求區分有符號和無符號數。
示例:
tips:
xorq %rdx, %rdx
是將 %rdx 置爲 0 的有效辦法,只佔3個字節。
3.5.5 特殊的算術操作
imulq 指令有兩種不同形式。其中一種是一個“雙操作數”乘法指令,另一種“單操作數”乘法指令(包含無符號乘法(mulq)和補碼乘法(imulq))。
這兩條指令都要求一個參數必須在寄存器 %rax 中,而另一個作爲指令的源操作數給出。乘積存放在寄存器 %rdx(高64位)和 %rax(低64位)中。
示例:
除法和取模操作。有符號除法指令 idivl 將寄存器 %rdx(高64位)和%rax(低64位)中的 128 位數作爲除數,而除數作爲指令的操作數給豬。指令將商存儲在寄存器 %rax 中,將餘數存儲在寄存器 %rdx 中(取模運算結果)。
特殊點:對於 64 位除數來說,這個值通常存放在 %rax 中, %rdx 的位一個設置全爲 0 (無符號運算)或者 %rax 的符號位(有符號運算)(可以用一個無操作數指令 cqto 來完成,該指令隱含讀出 %rax 的符號位並將結果複製到 %rdx 的所有位)。
示例:
3.6 控制(control)
條件碼(condition code)寄存器。
CF(carry flag): 進位標誌。最近的操作使最高位產生了進位。可用來檢查無符號操作的溢出。
ZF(zero flag):零標誌。最近的操作得出的結果爲零。
SF(signed flag):符號標誌。最近的操作得到的結果爲負數。
OF(overflow flag):溢出標誌。最近的操作導致一個補碼溢出——正溢出或負溢出。
leaq 指令不會改變任何條件碼,因爲它是用來進行地址計算的。
3.6.2 訪問條件碼
條件碼通常不會直接讀取,常用的使用方法有三種:
1) 可以根據條件碼的某種組合,將一個字節設置爲 0 或者 1;
2)可以條件跳轉到程序的某個其他的部分;
3)可以有條件地傳送數據。
3.6.3 跳轉指令
3.6.4 跳轉指令的編碼
3.6.5 用條件控制來實現條件分析
將條件表達式和語句從C語言翻譯成機器代碼,最常用的方式就結合有條件和無條件跳轉。
C語言中的 if-else 語句的通用形式模版如下:
if (test-expr)
then-statement
else
else-statement
對應彙編實現通常會使用下面這種形式。以C語言語法來描述控制流:
t = test-expr;
if (!t)
goto false;
then-statement;
goto done;
false:
else-statement;
done:
3.6.6 用條件傳送來實現條件分支
實現條件操作的傳統方法是通過使用控制的條件轉移(條件滿足,執行A;否則執行B)。
一種替代的策略是使用數據的條件轉移(在一定條件下,事先把條件結果列出來,根據條件直接選取數據結果來執行下一路徑)。
示例(ret 返回的參數存放在 %rax 中):
分支預測錯誤的處罰
條件傳送指令
如果條件分支中任意一個可能產生錯誤條件或者副作用,將會導致非法的行爲。
3.6.7 循環
do-while 循環
while 循環
示例:
for 循環
for 循環的通用格式:
for (init-expr; test-expr; update-expr)
body-statement
等價於:
init-expr:
while(test-expr){
body-statement
update-expr;
}
跳轉到中間的策略 goto 代碼:
init-expr;
goto test;
loop:
body-statement;
update-expr;
test:
t = test-expt;
if(t)
goto loop;
而 guarded-do 策略得到:
init-expr;
t = test-expr;
if(!t)
goto done;
loop:
body-statement;
t = test-expt;
if(t)
goto loop;
done:
綜上所述,C語言中三種形式的所有循環——do-while、while、和for都可以用一種簡單的策略來翻譯,產生包含一個或多個條件分支的代碼。控制的條件轉移提供將循環翻譯成機器代碼的基本機制。
3.6.8 switch 語句
switch(開關)語句可以根據一個整數索引值進行多重分支(multiway branching)。
跳轉表是一個數組,表項 i 是一個代碼段的地址,這個代碼段實現當開關索引值等於 i 時程序應該採取的動作。
示例:
跳轉表
3.7 過程
過程是軟件中的一種很重要的抽象。它提供了一種封裝代碼的方式,用一組指定的參數和一個可選的返回值實現了某種功能。
3.7.1 運行時棧
C語言使用棧數據結構提供的後進先出的內存管理原則。
3.7.2 轉移控制
將控制從函數P轉移到函數Q只需要簡單地把程序計數器(PC)設置爲Q的代碼的起始位置。
call 指令有一個目標,即指明被調用過程起始的指令地址。
示例:
3.7.3 數據傳送
過程調用還可能包括把數據作爲參數傳遞,而從過程返回還有可能包括返回一個值。
超出6個參數傳遞的例子:
3.7.4 棧上的局部存儲
局部數據必須存放在內存中,常見的情況有:
1) 寄存器不足夠存放所有的本地數據;
2) 對一個局部變量使用地址運算符 ’&‘ ,因此必須能夠爲它產生一個地址;
3) 某些局部變量是數據或結構,因此必須能夠通過數據或結構引用被訪問到。
3.7.5 寄存器中的局部存儲空間
寄存器是唯一被所有過程共享的資源。
據慣例,寄存器 %rbx %rbp 和 %r12~%r15 被劃分爲被調用者保存寄存器。所有其他的寄存器,除了棧指針 %rsp, 都分類爲調用者保存寄存器。
示例:
3.7.6 遞歸過程
示例:
3.8 數組分配和訪問
C語言的數組是一種將標量數據聚集成更大數據類型的方式。
3.8.1 基本原則
對於數據類型 T 和整型常數 N
,聲明如下:
T A[N]
;
示例:
3.8.2 指針運算
C語言允許對指針進行運算,而計算出來的值會根據該指針引用的數據類型的大小進行伸縮。
單操作數操作符&
和*
可以產生指針和間接引用指針。
3.8.3 嵌套的數組
當我們創建數組的數組時,數組分配和引用的一般原則也是成立的。例如:
int A[5][3]
等價於
typedef int row3_t[3];
row3_t A[5];
公式一:
示例:
3.8.4 定長數組
C語言編譯器能夠優化定長多爲數組上的操作代碼。
假設定義如下數組:
#define N 16
.typedef int fix_matrix[N][N];
示例:
3.8.5 變長數組
假設變長數組的聲明如下:
int A[
expr1][
expr2]
它可以作爲一個局部變量,也可以作爲一個函數的參數;通常對表達式expr1和expr2求值來確定數組的維度。例如訪問 n x n
數組的元素 i , j ,可以寫如下函數:
int var_ele(long n, int A[n][n], long i, long j){
return A[i][j];
}
參數 n 必須在參數 A[n][n] 之前。
如下代碼,計算兩個 n x n 矩陣 A 和 B 乘積的元素 i, k。
彙編代碼如下
Registers: n in %rdi, Arow in %rsi, Bptr in %rcx
4n in %r9, result in %eax, j in %edx
.L24:
movl, (%rsi, %rdx, 4), %r8d ;Read Arow[j]
imull (%rcx), %r8d ;Multiply by *Bptr
addl %r8d, %eax ;Add to result
addq $1, %rdx ;j++
addq %r9, %rcx ;Bptr += n
cmpq %rdi, %rcx ;Compare j:n
jne .L24
3.9 異質的數據結構
C語言提供了兩種將不同類型的對象組合到一起創建數據類型的機制:
結構(structure),用關鍵字struct來聲明,將多個對象集合到一個單位中;
聯合(union),用關鍵子union來聲明,允許用幾種不同的類型來引用一個對象。
3.9.1 結構
示例:
另外,我們可以在一條語句中既聲明變量又初始化它的手段:
struct rect r = {0, 0, 10, 20, 0xFF00FF};
返回長方形面積的示例:
long area(struct rect *rp)
{
return (*rp).width * (*rp).height;
}
rp -> width
等價於 (*rp).width
。
3.9.2 聯合
聯合允許以多種類型來引用一個對象。
考慮如下聲明:
struct S3
{
char c;
int i[2];
double v;
}
union U3
{
char c;
int i[2];
double v;
}
可以看出 類型 union U3 *
的指針 p
,p->c
、p->i[0]
和p->v
引用的都是數據結構的起始位置。一個聯合的總的大小等於它最大字段的大小。
3.9.3 數據對齊
許多計算機系統對基本數據類型的合法地址做出了一些限制,要求某種類型對象的地址必須是某個值K(通常是2、4或8的倍數)。這種對齊限制簡化了形成處理器和內存系統之間接口的硬件設計。
K | 類型 |
---|---|
1 | char |
2 | short |
4 | int, float |
8 | long, double,chat* |
例如跳轉表的彙編聲明包含下列指令:
.align 8
這就保證了它後面的數據的起始地址是 8 的倍數。
示例:
藍色部分爲爲對齊而填充的地址空間。
3.10 在機器級程序中將控制與數據結合起來
3.10.1 理解指針
指針以一種統一方式,對不同數據結構中的元素產生引用。
1) 每個指針都對應一個類型。(指針類型不是機器代碼中的一部分;只是C語言提供的一種抽象,幫助程序員避免尋址錯誤。)
2) 每個指針都有一個值。這個值是某個指定類型的對象的地址。特殊的 NULL(0)值代表該指針沒有指向任何地方。
3) 指針用‘&’運算符創建。
4) * 操作符是用來間接引用指針 。
5) 數組與指針緊密聯繫。一個數組的名字可以像指針變量一樣引用(但是不能修改)。如,a[3]
等價於*(a+3)
。
數組引用和指針運算都需要用對象大小對偏移量進行伸縮。
6) 將指針從一種類型強制轉換成另一種類型,只改變它的類型,而不改變它的值。
例如:
char *p = 'c' ;
(int *)p + 7 的結果爲 c + 28;
(int *)(p + 7) 的結果爲 c + 7。
(強制類型轉換的優先級高於加法)
7) 指針也可以指向函數。這提供了一個很強大的存儲和像代碼傳遞引用的功能,這些引用可以被程序的某個其他部分調用。
例如:
int fun(int x, int *p);
/** 聲明指針 fp ,將它賦值爲這個函數 */
int (*fp)(int, int *);
fp = fun;
/** 函數調用 */
int y = 1;
int result = fp(3, &y);
函數指針的值是該函數機器代碼表示中第一條指令的地址。
3.10.2 應用:使用 GDB 調試器
先運行OBJDUMP 來獲得程序的反彙編版本。如下命令行來啓動 GDB:
linux> gdb prog
通常的方法是在程序感興趣的地方附近設置斷點。
3.10.3 內存越界引用和緩衝區溢出
緩衝區溢出(buffer overflow)。在棧中分配某個字符數組來保存字符串,但是字符串的長度超出了爲數組分配的空間。
示例:
echo
對應的彙編:
越界會破壞的信息:
緩衝區溢出的一個更加致命的使用就是讓程序執行它本來不願意執行的函數。這是一種最常見的通過計算機網絡攻擊系統安全的方法。
3.10.4 對抗緩衝區溢出攻擊
1. 棧隨機化
爲了在系統中插入攻擊代碼,攻擊者既要插入代碼,也要插入指向這段代碼的指針,這個指針也是攻擊字符串的一部分。產生這個指針需要知道這個字符串放置的棧地址。
棧隨機化的思想使得棧的位置在程序每次運行時都有變化。這類技術稱爲地址空間佈局隨機化(Address-Space Layout Randomization),簡稱ASLR。
通常攻擊者使用”空操作雪橇(nop sled)“,使程序”滑過“目標序列,即在實際攻擊代碼前插入一段很長的nop
(讀作“no op”
,no operation
的縮寫)指令。
2. 棧破壞檢測
計算機的第二道防線是能夠檢測到何時棧已經被破壞。
GCC提供一種棧保護者機制,來檢測緩衝區越界。其思想是在棧幀中任何局部緩衝區與棧狀態之間存儲一個特殊的金絲雀(canary),也稱爲哨兵值(guard value),是在程序每次運行時隨機產生的。
3. 限制可執行代碼區域
最後一招是消除攻擊者向系統插入可執行代碼的能力。
3.10.5 支持變長棧幀
前面所講的各種函數的機器級代碼,都有一個共同點,即編譯器能夠預先確定需要爲棧幀分配多少空間。
下面示例爲局部存儲是變長的。
%rbp稱爲幀指針(frame pointer)(有時稱爲基址幀(base pointer);
leave
指令將棧幀指針恢復到它之前的值(第20行)。等價於:
movq %rbp, %rsp ;Set stack pointer to begining of frame
popq %rbp ;Restore saved %rbp and set stack ptr to end of caller's frame
練習及答案(紅色部分):
根據圖3-43和圖3-44填寫下表(要細心啊,我算了好久,答案對不上,最後面發現算錯了):
n | s1 | s2 | p | e1 | e2 |
---|---|---|---|---|---|
5 | 2065 | 2017 |
2024 |
1 |
7 |
6 | 2064 | 2000 |
2000 |
16 |
0 |
3.11 浮點代碼
處理器的浮點體系結構包括多個方面,會影響對浮點數據操作的程序如何被映射到機器上,包括:
1) 如何存儲和訪問浮點數據。通常是通過某種寄存器方式來完成。
2) 對浮點數據操作的指令。
3) 想函數傳遞浮點數參數和從函數返回浮點數結構的規則。
4) 函數調用過程保持寄存器的規則——例如,一些寄存器被指定爲調用者保存
,而其他的被指定爲被調用者保存
。
AVX
浮點體系結構允許數據存儲在16
個YMM
寄存器中,它們的名字爲%ymm0~%ymm15
。每個YMM
寄存器都是256
位(32
位)。
3.11.1 浮點傳送和轉換操作
引用內存的指令是標量
指令,意味着它們只對單個而不是一組封裝好的數據值進行操作。
GCC
只用標量傳送操作從內存傳送數據到XMM
寄存器或從XMM
寄存器傳送數據到內存。
示例:
把浮點值轉換成整數時,指令會執行截斷(truncation)
,把值指向0
進行舍入,這是C
和大多數其他編程語言的要求。
圖3-48
中的指令把整數轉換成浮點數。第一個操作數讀自於內存或一個通用目的寄存器。第二個操作數只會影響結果的高位字節
。
例如如下指令:
vcvtsi2sdq %rax, %xmm1, %xmm1
該指令從寄存器%rax
讀出一個長整數,把它轉換成數據類型double
,並把結果存放進XMM
寄存器%xmm1
的低字節中。
假設%xmm0
的低位4字節保存着一個單精度值,使用如下指令:
vcvtss2sd %xmm0, %xmm0, %xmm0
把它轉換成一個雙精度值,並將結果存儲在寄存器%xmm0
的低8字節。
GCC
生成的代碼:
Conversion from single to double precision
1 vunpcklps %xmm0, %xmm0, %xmm0 `Replace first vector element`
2 vcvtps2pd %xmm0, %xmm0 `Convert two element to double`
vunpcklps
指令通常用來交叉放置來自兩個XMM
寄存器的值,把它們存儲到第三個寄存器中(例如:源R1 = [s3, s2, s1, s0]
, 源R2 = [d3, d2, d1, d0]
,那麼目的寄存器R3 = [s1, d1, s0, d0]
。)。
對於雙精度轉換爲單精度,·GCC
會產生類似的代碼:
Conversion from double to single precsion
1 vmovddup %xmm0, %xmm0 `Replicate first vector element`
2 vcvtpd2psx %xmm0, %xmm0 `Convert two vector elements to single`
假設這些指令開始執行前寄存器%xmm0
保存着兩個雙精度值[x1, x2]
。然後vmovddup
指令把它設置爲[x0, x0]
。vcvtpd2psx
指令把這兩個值轉換成單精度,再存放到該寄存器的低位一般中,並將高位一半置0,得到結果[0.0, 0.0, x0, x0]
。
fcvt
的所有參數都是通過通用寄存器傳遞的,因爲它們既不是整數也不是指針。
浮點傳送和轉換操作指令彙總
指令 | 源1 | 源2 | 目的 | 描述 |
---|---|---|---|---|
vmovss |
M32 |
NULL |
X |
傳送單精度數 |
vmovss |
X |
NULL |
M32 |
傳送單精度數 |
vmovsd |
M64 |
NULL |
X |
傳送雙精度數 |
vmovsd |
X |
NULL |
M64 |
傳送雙精度數 |
vmovaps |
X |
NULL |
X |
傳送 對齊的封裝好的單精度數 |
vmovapd |
X |
NULL |
X |
傳送 對齊的封裝好的雙精度數 |
vcvttss2si |
X/M32 |
NULL |
R32 |
用截斷的方法把單精度數轉換成整數 |
vcvttsd2si |
X/M64 |
NULL |
R32 |
用截斷的方法把雙精度數轉換成整數 |
vcvttss2siq |
X/M32 |
NULL |
R64 |
用截斷的方法把單精度數轉換成四字整數 |
vcvttsd2siq |
X/M64 |
NULL |
R64 |
用截斷的方法把雙精度數轉換成四字整數 |
vcvtsi2ss |
M32/R32 |
X |
X |
把整數轉換成單精度數 |
vcvtsi2sd |
M32/R32 |
X |
X |
把整數轉換成雙精度數 |
vcvtsi2ssq |
M64/R64 |
X |
X |
把四字整數轉換成單精度數 |
vcvtsi2sdq |
M64/R64 |
X |
X |
把四字整數轉換成雙精度數 |
vcvtps2pd |
X1 |
NULL |
X2 |
**把 X1中兩個低位單精度值擴展成 X2中的兩個雙精度值 |
vunpcklps |
X1 |
X2 |
X3 |
交叉放置 X1和 X2的值存儲到 X3中 |
注:NULL
表示沒有該源。
3.11.2 過程中的浮點代碼
在x86-64中,XMM
寄存器用來向函數傳遞浮點參數,以及從函數返回浮點值。
1) XMM
寄存器%xmm0~%xmm7
最多可以傳遞8個浮點參數(額外的可以通過棧傳遞)。
2) 函數使用寄存器xmm0
來返回浮點值。
3) 所有的XMM
寄存器都是調用者保存的。被調用者可以不用保存就覆蓋這些寄存器中任一個。
示例:
3.11.3 浮點運算操作
圖3-49
描述了一組執行算術運算的標量AVX2
浮點指令。
3.11.4 定義和使用浮點常數
和整數運算操作不同,AVX
浮點操作不能以立即數值作爲操作數。
示例(`需重點理解
):
解析:3435973837(0xcccccccd)
,1073532108(0x3ffccccc)
,從高位字節,可以抽取0x3ff(1023)
,減去偏移量2^(k-1)-1=2^(11-1)-1=1023
,得到指數0
。小數字段爲0xccccccccccccd(3602879701896397)
,然後除以2^(52)=4503599627370496
,最後得到小數0.8
,加上隱含的1
,得到1.8
,最後得數爲1.8*2^0=1.8
。
3.11.5 在浮點代碼中使用位級操作
單精度 | 雙精度 | 效果 | 描述 |
---|---|---|---|
vxorps |
vorps |
D <- S2^S1 |
位級異或(EXCLUSIVE-OR ) |
vandps |
andpd |
D <- S2&S1 |
位級與(AND ) |
圖3-50 對封裝數據的位級操作(這些指令對一個XMM寄存器中的所有128
位進行布爾操作)
3.11.6 浮點比較操作
AVX
提供了兩條比較浮點值的指令(類似與CMP
指令,但是操作數順序相反):
指令 | 基於 | 描述 |
---|---|---|
ucomiss S1, S2 |
S2-S1 |
比較單精度值 |
ucomisd S1, S2 |
S2-S1 |
比較雙精度值 |
浮點比較指令會設置三個條件碼:零標誌位 ZF
、進位標誌位CF
和奇偶標記位PF
。
當任一操作數爲NaN
時,就會出現*無序的情況。可以通過奇偶標誌位發現這種情況。通常jp(jump on parity)
指令是條件跳轉,條件就是浮點比較得到一個無序的結果。
示例:
解析如下圖