因此,我今天想問一個相當存在的問題,我覺得它好像大多數程序員跳過,只是接受一些有用的東西,而沒有真正地問「如何」工作的問題。問題很簡單:> =操作符如何編譯爲機器碼,機器碼是什麼樣的?在最底層,它必須比測試更大,並且與「同等」測試混合在一起。但這是如何實現的?對此的思考似乎相當自相矛盾,因爲在最底層不能進行>或==測試。還需要別的東西。我想知道這是什麼。數學平等運算符如何在機器代碼級別處理
計算機如何測試平等和超過基本水平?
因此,我今天想問一個相當存在的問題,我覺得它好像大多數程序員跳過,只是接受一些有用的東西,而沒有真正地問「如何」工作的問題。問題很簡單:> =操作符如何編譯爲機器碼,機器碼是什麼樣的?在最底層,它必須比測試更大,並且與「同等」測試混合在一起。但這是如何實現的?對此的思考似乎相當自相矛盾,因爲在最底層不能進行>或==測試。還需要別的東西。我想知道這是什麼。數學平等運算符如何在機器代碼級別處理
計算機如何測試平等和超過基本水平?
確實沒有>
或==
這樣的測試。相反,彙編程序中的最低級別比較工作爲binary subtraction。 在x86上,整數比較的操作碼是CMP
。這是真的一條指令統治他們。它是如何工作的,例如在80386 Programmer's reference manual描述:
CMP
減去第一,但是,不像SUB
指令,不存儲結果的第二個操作數;只有標誌被改變。
CMP
通常與條件跳轉和SETcc
指令一起使用。 (提供的簽名和未簽名標誌測試列表請參見附錄D.)如果大於一個字節的操作數與即時字節進行比較,則字節值首先被符號擴展。
基本上,CMP A, B
(在Intel operand ordering)計算A - B
,然後丟棄結果。但是,在x86 ALU中,算術運算根據操作結果在CPU的標誌寄存器內設置條件標誌。相關算術運算的標誌是
Bit Name Function
0 CF Carry Flag -- Set on high-order bit carry or borrow; cleared
otherwise.
6 ZF Zero Flag -- Set if result is zero; cleared otherwise.
7 SF Sign Flag -- Set equal to high-order bit of result (0 is
positive, 1 if negative).
11 OF Overflow Flag -- Set if result is too large a positive number
or too small a negative number (excluding sign-bit) to fit in
destination operand; cleared otherwise.
例如,如果計算的結果爲零,則Zero Flag ZF
被設置。 CMP A, B
執行A - B
並丟棄結果。如果f A == B
減法的結果爲0。因此ZF
只有在操作數相等時纔會被設置,否則被清零。
進位標誌CF
將被設置當且僅當無符號減法將導致borrow,即A - B
將< 0
如果A
和B
被認爲unsigned
號碼和A < B
。只要結果的MSB位設置
註冊標誌。這意味着作爲有符號數的結果在2的補碼中被認爲是負的。但是,如果考慮8位減法01111111
(127) - 10000000
(-128),則結果爲11111111
,其解釋爲8位帶符號2的補碼數爲-1
,即使127 - (-128)
應該是255
。發生有符號整數溢出單獨的符號標誌並不能單獨表明哪個有符號數量更大 - OF
溢出標誌指示在上一次算術運算中是否發生了有符號溢出。
現在,根據使用該位置的地方,使用Byte Set on Condition SETcc
或Jump if Condition is Met Jcc
指令來解碼標誌並對其執行操作。如果布爾值用於設置變量,那麼聰明的編譯器將使用SETcc
; Jcc
將會更好地匹配if
... else
。
現在,有2個選擇爲>=
:要麼我們希望有一個符號比較或者無符號比較。
int a, b;
bool r1, r2;
unsigned int c, d;
r1 = a >= b; // signed
r2 = c >= d; // unsigned
在英特爾組件的無符號不等式條件的名稱使用的話上述和下面;對於簽訂平等條件下使用的話更大和少。因此,對於r2
編譯器可以決定使用集上高於或等於,即SETAE
,如果(CF=0)
目標字節,其設置爲1。對於r1
結果將通過SETGE
被解碼 - 上大於或等於,這意味着(SF=OF)
設置字節 - 即減法解釋爲2的補碼的結果是肯定的,而不溢出,或溢出發生負。
最後一個例子:
#include <stdbool.h>
bool gte_unsigned(unsigned int a, unsigned int b) {
return a >= b;
}
上的x86-64 Linux的所得優化的代碼是:
cmp edi, esi
setae al
ret
同樣地,對於符號比較
bool gte_signed(int a, int b) {
return a >= b;
}
所得到的組件是
cmp edi, esi
setge al
ret
這裏有一個簡單的C函數:
bool lt_or_eq(int a, int b)
{
return (a <= b);
}
在X86-64,GCC編譯這:
.file "lt_or_eq.c"
.text
.globl lt_or_eq
.type lt_or_eq, @function
lt_or_eq:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %eax
cmpl -8(%rbp), %eax
setle %al
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size lt_or_eq, .-lt_or_eq
的重要組成部分,是cmpl -8(%rbp), %eax; setle %al;
序列。基本上,它使用cmp
指令以數字方式比較兩個參數,並根據該比較設置零標誌和進位標誌的狀態。然後它使用setle
來決定是否將%al寄存器設置爲0
或1
,具體取決於這些標誌的狀態。調用者從寄存器獲取返回值。
首先計算機需要確定數據的類型。在像C這樣的語言中,這是在編譯時,python會在運行時調度到不同類型的特定測試。假設我們從一個彙編語言來了,我們知道我們是在比較值是整數,該編譯器將確保該valuses在寄存器中,然後問題:
SUBS r1, r2
BGE @target
減去寄存器,然後檢查零/不流。這些指令在CPU上運行。 (我在這裏假設是類似ARM的,有很多變體)。
編譯器將使用非破壞性比較操作(如果有的話)。 – EJP
'SETLE'根本不檢查進位,而是設置1(ZF = 1和SF <> OF) –