# 第21章 在32位環境中的64位值
在32位環境中的通用寄存器是32位的,所以64位值轉化為一對32位值。
## 21.1參數的傳遞,加法,減法
```
#!cpp
#include <stdint.h>
uint64_t f1 (uint64_t a, uint64_t b)
{
return a+b;
};
void f1_test ()
{
#ifdef __GNUC__
printf ("%lld
", f1(12345678901234, 23456789012345));
#else
printf ("%I64d
", f1(12345678901234, 23456789012345));
#endif
};
uint64_t f2 (uint64_t a, uint64_t b)
{
return a-b;
};
```
代碼 21.1: MSVC 2012 /Ox /Ob1
```
#!bash
_a$ = 8 ; size = 8
_b$ = 16 ; size = 8
_f1 PROC
mov eax, DWORD PTR _a$[esp-4]
add eax, DWORD PTR _b$[esp-4]
mov edx, DWORD PTR _a$[esp]
adc edx, DWORD PTR _b$[esp]
ret 0
_f1 ENDP
_f1_test PROC
push 5461 ; 00001555H
push 1972608889 ; 75939f79H
push 2874 ; 00000b3aH
push 1942892530 ; 73ce2ff2H
call _f1
push edx
push eax
push OFFSET $SG1436 ; ’%I64d’, 0aH, 00H
call _printf
add esp, 28 ; 0000001cH
ret 0
_f1_test ENDP
_f2 PROC
mov eax, DWORD PTR _a$[esp-4]
sub eax, DWORD PTR _b$[esp-4]
mov edx, DWORD PTR _a$[esp]
sbb edx, DWORD PTR _b$[esp]
ret 0
_f2 ENDP
```
我們可以看到在函數f1_test()中每個64位值轉化為2個32位值,高位先轉,然后是低位。加法和減法也是如此。
當進行加法操作時,低32位部分先做加法。如果相加過程中產生進位,則設置CF標志。下一步通過ADC指令加上高位部分,如果CF置1了就增加1。
減法操作也是如此。第一個SUB操作也會導致CF標志的改變,并在隨后的SBB操作中檢查:如果CF置1了,那么最終結果也會減去1。
在32位環境中,64位的值是從EDX:EAX這一對寄存器的函數中返回的。可以很容易看出f1()函數是如何轉化為printf()函數的。
代碼 21.2: GCC 4.8.1 -O1 -fno-inline
```
#!bash
_f1:
mov eax, DWORD PTR [esp+12]
mov edx, DWORD PTR [esp+16]
add eax, DWORD PTR [esp+4]
adc edx, DWORD PTR [esp+8]
ret
_f1_test:
sub esp, 28
mov DWORD PTR [esp+8], 1972608889 ; 75939f79H
mov DWORD PTR [esp+12], 5461 ; 00001555H
mov DWORD PTR [esp], 1942892530 ; 73ce2ff2H
mov DWORD PTR [esp+4], 2874 ; 00000b3aH
call _f1
mov DWORD PTR [esp+4], eax
mov DWORD PTR [esp+8], edx
mov DWORD PTR [esp], OFFSET FLAT:LC0 ; "%lld12"
call _printf
add esp, 28
ret
_f2:
mov eax, DWORD PTR [esp+4]
mov edx, DWORD PTR [esp+8]
sub eax, DWORD PTR [esp+12]
sbb edx, DWORD PTR [esp+16]
ret
```
GCC代碼也是如此。
## 21.2乘法,除法
```
#!cpp
#include <stdint.h>
uint64_t f3 (uint64_t a, uint64_t b)
{
return a*b;
};
uint64_t f4 (uint64_t a, uint64_t b)
{
return a/b;
};
uint64_t f5 (uint64_t a, uint64_t b)
{
return a % b;
};
```
代碼 21.3: MSVC 2012 /Ox /Ob1
```
#!bash
_a$ = 8 ; size = 8
_b$ = 16 ; size = 8
_f3 PROC
push DWORD PTR _b$[esp]
push DWORD PTR _b$[esp]
push DWORD PTR _a$[esp+8]
push DWORD PTR _a$[esp+8]
call __allmul ; long long multiplication
ret 0
_f3 ENDP
_a$ = 8 ; size = 8
_b$ = 16 ; size = 8
_f4 PROC
push DWORD PTR _b$[esp]
push DWORD PTR _b$[esp]
push DWORD PTR _a$[esp+8]
push DWORD PTR _a$[esp+8]
call __aulldiv ; unsigned long long division
ret 0
_f4 ENDP
_a$ = 8 ; size = 8
_b$ = 16 ; size = 8
_f5 PROC
push DWORD PTR _b$[esp]
push DWORD PTR _b$[esp]
push DWORD PTR _a$[esp+8]
push DWORD PTR _a$[esp+8]
call __aullrem ; unsigned long long remainder
ret 0
_f5 ENDP
```
乘法和除法是更為復雜的操作,一般來說,編譯器會嵌入庫函數的calls來使用。
部分函數的意義:可參見附錄E。
Listing 21.4: GCC 4.8.1 -O3 -fno-inline
```
#!bash
_f3:
push ebx
mov edx, DWORD PTR [esp+8]
mov eax, DWORD PTR [esp+16]
mov ebx, DWORD PTR [esp+12]
mov ecx, DWORD PTR [esp+20]
imul ebx, eax
imul ecx, edx
mul edx
add ecx, ebx
add edx, ecx
pop ebx
ret
_f4:
sub esp, 28
mov eax, DWORD PTR [esp+40]
mov edx, DWORD PTR [esp+44]
mov DWORD PTR [esp+8], eax
mov eax, DWORD PTR [esp+32]
mov DWORD PTR [esp+12], edx
mov edx, DWORD PTR [esp+36]
mov DWORD PTR [esp], eax
mov DWORD PTR [esp+4], edx
call ___udivdi3 ; unsigned division
add esp, 28
ret
_f5:
sub esp, 28
mov eax, DWORD PTR [esp+40]
mov edx, DWORD PTR [esp+44]
mov DWORD PTR [esp+8], eax
mov eax, DWORD PTR [esp+32]
mov DWORD PTR [esp+12], edx
mov edx, DWORD PTR [esp+36]
mov DWORD PTR [esp], eax
mov DWORD PTR [esp+4], edx
call ___umoddi3 ; unsigned modulo
add esp, 28
ret
```
GCC的做法幾乎一樣,但是乘法代碼內聯在函數中,可認為這樣更有效。
GCC有一些不同的庫函數:參見附錄D
## 21.3右移
```
#!cpp
#include <stdint.h>
uint64_t f6 (uint64_t a)
{
return a>>7;
};
```
代碼 21.5: MSVC 2012 /Ox /Ob1
```
#!bash
_a$ = 8 ; size = 8
_f6 PROC
mov eax, DWORD PTR _a$[esp-4]
mov edx, DWORD PTR _a$[esp]
shrd eax, edx, 7
shr edx, 7
ret 0
_f6 ENDP
```
代碼 21.6: GCC 4.8.1 -O3 -fno-inline
```
#!bash
_f6:
mov edx, DWORD PTR [esp+8]
mov eax, DWORD PTR [esp+4]
shrd eax, edx, 7
shr edx, 7
ret
```
右移也是分成兩步完成:先移低位,然后移高位。但是低位部分通過指令SHRD移動,它將EDX的值移動7位,并從EAX借來1位,也就是從高位部分。而高位部分通過更受歡迎的指令SHR移動:的確,高位釋放出來的位置用0填充。
## 21.4從32位值轉化為64位值
```
#!cpp
#include <stdint.h>
int64_t f7 (int64_t a, int64_t b, int32_t c)
{
return a*b+c;
};
int64_t f7_main ()
{
return f7(12345678901234, 23456789012345, 12345);
};
```
代碼 21.7: MSVC 2012 /Ox /Ob1
```
#!bash
_a$ = 8 ; size = 8
_b$ = 16 ; size = 8
_c$ = 24 ; size = 4
_f7 PROC
push esi
push DWORD PTR _b$[esp+4]
push DWORD PTR _b$[esp+4]
push DWORD PTR _a$[esp+12]
push DWORD PTR _a$[esp+12]
call __allmul ; long long multiplication
mov ecx, eax
mov eax, DWORD PTR _c$[esp]
mov esi, edx
cdq ; input: 32-bit value in EAX; output: 64-bit value in EDX:EAX
add eax, ecx
adc edx, esi
pop esi
ret 0
_f7 ENDP
_f7_main PROC
push 12345 ; 00003039H
push 5461 ; 00001555H
push 1972608889 ; 75939f79H
push 2874 ; 00000b3aH
push 1942892530 ; 73ce2ff2H
call _f7
add esp, 20 ; 00000014H
ret 0
_f7_main ENDP
```
這里我們有必要將有符號的32位值從c轉化為有符號的64位值。無符號值的轉化簡單了當:所有的高位部分全部置0。但是這樣不適合有符號的數據類型:符號標志應復制到結果中的高位部分。這里用到的指令是CDQ,它從EAX中取出數值,將其變為64位并存放到EDX:EAX這一對寄存器中。換句話說,指令CDQ從EAX中獲取符號(通過EAX中最重要的位),并根據它來設置EDX中所有位為0還是為1。它的操作類似于指令MOVSX(13.1.1)。
代碼 21.8: GCC 4.8.1 -O3 -fno-inline
```
#!bash
_f7:
push edi
push esi
push ebx
mov esi, DWORD PTR [esp+16]
mov edi, DWORD PTR [esp+24]
mov ebx, DWORD PTR [esp+20]
mov ecx, DWORD PTR [esp+28]
mov eax, esi
mul edi
imul ebx, edi
imul ecx, esi
mov esi, edx
add ecx, ebx
mov ebx, eax
mov eax, DWORD PTR [esp+32]
add esi, ecx
cdq ; input: 32-bit value in EAX; output: 64-bit value in EDX:EAX
add eax, ebx
adc edx, esi
pop ebx
pop esi
pop edi
ret
_f7_main:
sub esp, 28
mov DWORD PTR [esp+16], 12345 ; 00003039H
mov DWORD PTR [esp+8], 1972608889 ; 75939f79H
mov DWORD PTR [esp+12], 5461 ; 00001555H
mov DWORD PTR [esp], 1942892530 ; 73ce2ff2H
mov DWORD PTR [esp+4], 2874 ; 00000b3aH
call
_f7
add esp, 28
ret
```
GCC生成的匯編代碼跟MSVC一樣,但是在函數中內聯乘法代碼。 更多:32位值在16位環境中(30.4)
- 第一章 CPU簡介
- 第二章 Hello,world!
- 第三章? 函數開始和結束
- 第四章 棧
- Chapter 5 printf() 與參數處理
- Chapter 6 scanf()
- CHAPER7 訪問傳遞參數
- Chapter 8 一個或者多個字的返回值
- Chapter 9 指針
- Chapter 10 條件跳轉
- 第11章 選擇結構switch()/case/default
- 第12章 循環結構
- 第13章 strlen()
- Chapter 14 Division by 9
- chapter 15 用FPU工作
- Chapter 16 數組
- Chapter 17 位域
- 第18章 結構體
- 19章 聯合體
- 第二十章 函數指針
- 第21章 在32位環境中的64位值
- 第二十二章 SIMD
- 23章 64位化
- 24章 使用x64下的SIMD來處理浮點數
- 25章 溫度轉換
- 26章 C99的限制
- 27章 內聯函數
- 第28章 得到不正確反匯編結果
- 第29章 花指令
- 第30章 16位Windows
- 第31章 類
- 三十二 ostream