# 25章 溫度轉換
另一個在初學者的編程書中常見的例子是溫度轉換程序,例如將華氏度轉為攝氏度,或者反過來。
我也添加了一個簡單的錯誤處理: 1)我們應該檢查用戶是否輸入了正確的數字 2)我們應該檢查攝氏度是否低于-273゜C,因為這比絕對零度還低,學校物理課上的東西應該都還記得。 exit()函數將立即終止程序,而不會回到調用者函數。
## 25.1 整數值
```
#!cpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
int celsius, fahr;
printf ("Enter temperature in Fahrenheit:\n");
if (scanf ("%d", &fahr)!=1)
{
printf ("Error while parsing your input\n");
exit(0);
};
celsius = 5 * (fahr-32) / 9;
if (celsius<-273)
{
printf ("Error: incorrect temperature!\n");
exit(0);
};
printf ("Celsius: %d\n", celsius);
};
```
### 25.1.1 MSVC 2012 x86 /Ox
清單25.1: MSVC 2012 x86 /Ox
```
#!bash
$SG4228 DB ’Enter temperature in Fahrenheit:’, 0aH, 00H
$SG4230 DB ’%d’, 00H
$SG4231 DB ’Error while parsing your input’, 0aH, 00H
$SG4233 DB ’Error: incorrect temperature!’, 0aH, 00H
$SG4234 DB ’Celsius: %d’, 0aH, 00H
_fahr$ = -4 ; size = 4
_main PROC
push ecx
push esi
mov esi, DWORD PTR __imp__printf
push OFFSET $SG4228 ; ’Enter temperature in Fahrenheit:’
call esi ; call printf()
lea eax, DWORD PTR _fahr$[esp+12]
push eax
push OFFSET $SG4230 ; ’%d’
call DWORD PTR __imp__scanf
add esp, 12 ; 0000000cH
cmp eax, 1
je SHORT $LN2@main
push OFFSET $SG4231 ; ’Error while parsing your input’
call esi ; call printf()
add esp, 4
push 0
call DWORD PTR __imp__exit
$LN9@main:
$LN2@main:
mov eax, DWORD PTR _fahr$[esp+8]
add eax, -32 ; ffffffe0H
lea ecx, DWORD PTR [eax+eax*4]
mov eax, 954437177 ; 38e38e39H
imul ecx
sar edx, 1
mov eax, edx
shr eax, 31 ; 0000001fH
add eax, edx
cmp eax, -273 ; fffffeefH
jge SHORT $LN1@main
push OFFSET $SG4233 ; ’Error: incorrect temperature!’
call esi ; call printf()
add esp, 4
push 0
call DWORD PTR __imp__exit
$LN10@main:
$LN1@main:
push eax
push OFFSET $SG4234 ; ’Celsius: %d’
call esi ; call printf()
add esp, 8
; return 0 - at least by C99 standard
xor eax, eax
pop esi
pop ecx
ret 0
$LN8@main:
_main ENDP
```
關于這個我們可以說的是:
```
?printf()的地址先被載入了ESI寄存器中,所以printf()調用的序列會被CALL ESI處理,這是一個非常著名的編譯器技術,當代碼中存在多個序列調用同一個函數的時候,并且/或者有空閑的寄存器可以用上的時候,編譯器就會這么做。
?我們知道ADD EAX,-32指令會把EAX中的數據減去32。 EAX = EAX + (-32)等同于 EAX = EAX - 32,因此編譯器決定用ADD而不是用SUB,也許這樣性能比較高吧。
?LEA指令在值應當乘以5的時候用到了: lea ecx, DWORD PTR [eax+eax*4]。 是的,i + i * 4是等同于i*5的,而且LEA比IMUL運行的要快。 還有,SHL EAX,2/ ADD EAX,EAX指令對也可以替換這句,而且有些編譯器就是會這么優化。
?用乘法做除法的技巧也會在這兒用上。
?雖然我們沒有指定,但是main()函數依然會返回0。C99規范告訴我們[15章, 5.1.2.2.3] main()將在沒有return時也會照常返回0。 這個規則僅僅對main()函數有效。 雖然MSVC并不支持C99,但是這么看說不好他還是做到了一部分呢?
```
## 25.1.2 MSVC 2012 x64 /Ox
生成的代碼幾乎一樣,但是我發現每個exit()調用之后都有INT 3。
```
#!bash
xor ecx, ecx
call QWORD PTR __imp_exit
int 3
```
INT 3是一個調試器斷點。 可以知道的是exit()是永遠不會return的函數之一。所以如果他“返回”了,那么估計發生了什么奇怪的事情,也是時候啟動調試器了。
## 25.2 浮點數值
清單11.1: MSVC 2010
```
#!cpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
double celsius, fahr;
printf ("Enter temperature in Fahrenheit:\n");
if (scanf ("%lf", &fahr)!=1)
{
printf ("Error while parsing your input\n");
exit(0);
};
celsius = 5 * (fahr-32) / 9;
if (celsius<-273)
{
printf ("Error: incorrect temperature!\n");
exit(0);
};
printf ("Celsius: %lf\n", celsius);
};
```
MSVC 2010 x86使用FPU指令...
清單25.2: MSVC 2010 x86 /Ox
```
#!bash
$SG4038 DB ’Enter temperature in Fahrenheit:’, 0aH, 00H
$SG4040 DB ’%lf’, 00H
$SG4041 DB ’Error while parsing your input’, 0aH, 00H
$SG4043 DB ’Error: incorrect temperature!’, 0aH, 00H
$SG4044 DB ’Celsius: %lf’, 0aH, 00H
__real@c071100000000000 DQ 0c071100000000000r ; -273
__real@4022000000000000 DQ 04022000000000000r ; 9
__real@4014000000000000 DQ 04014000000000000r ; 5
__real@4040000000000000 DQ 04040000000000000r ; 32
_fahr$ = -8 ; size = 8
_main PROC
sub esp, 8
push esi
mov esi, DWORD PTR __imp__printf
push OFFSET $SG4038 ; ’Enter temperature in Fahrenheit:’
call esi ; call printf
lea eax, DWORD PTR _fahr$[esp+16]
push eax
push OFFSET $SG4040 ; ’%lf’
call DWORD PTR __imp__scanf
add esp, 12 ; 0000000cH
cmp eax, 1
je SHORT $LN2@main
push OFFSET $SG4041 ; ’Error while parsing your input’
call esi ; call printf
add esp, 4
push 0
call DWORD PTR __imp__exit
$LN2@main:
fld QWORD PTR _fahr$[esp+12]
fsub QWORD PTR __real@4040000000000000 ; 32
fmul QWORD PTR __real@4014000000000000 ; 5
fdiv QWORD PTR __real@4022000000000000 ; 9
fld QWORD PTR __real@c071100000000000 ; -273
fcomp ST(1)
fnstsw ax
test ah, 65 ; 00000041H
jne SHORT $LN1@main
push OFFSET $SG4043 ; ’Error: incorrect temperature!’
fstp ST(0)
call esi ; call printf
add esp, 4
push 0
call DWORD PTR __imp__exit
$LN1@main:
sub esp, 8
fstp QWORD PTR [esp]
push OFFSET $SG4044 ; ’Celsius: %lf’
call esi
add esp, 12 ; 0000000cH
; return 0
xor eax, eax
pop esi
add esp, 8
ret 0
$LN10@main:
_main ENDP
```
但是MSVC從2012年開始又改成了使用SIMD指令:
清單25.3: MSVC 2010 x86 /Ox
```
#!bash
$SG4228 DB ’Enter temperature in Fahrenheit:’, 0aH, 00H
$SG4230 DB ’%lf’, 00H
$SG4231 DB ’Error while parsing your input’, 0aH, 00H
$SG4233 DB ’Error: incorrect temperature!’, 0aH, 00H
$SG4234 DB ’Celsius: %lf’, 0aH, 00H
__real@c071100000000000 DQ 0c071100000000000r ; -273
__real@4040000000000000 DQ 04040000000000000r ; 32
__real@4022000000000000 DQ 04022000000000000r ; 9
__real@4014000000000000 DQ 04014000000000000r ; 5
_fahr$ = -8 ; size = 8
_main PROC
sub esp, 8
push esi
mov esi, DWORD PTR __imp__printf
push OFFSET $SG4228 ; ’Enter temperature in Fahrenheit:’
call esi ; call printf
lea eax, DWORD PTR _fahr$[esp+16]
push eax
push OFFSET $SG4230 ; ’%lf’
call DWORD PTR __imp__scanf
add esp, 12 ; 0000000cH
cmp eax, 1
je SHORT $LN2@main
push OFFSET $SG4231 ; ’Error while parsing your input’
call esi ; call printf
add esp, 4
push 0
call DWORD PTR __imp__exit
$LN9@main:
$LN2@main:
movsd xmm1, QWORD PTR _fahr$[esp+12]
subsd xmm1, QWORD PTR __real@4040000000000000 ; 32
movsd xmm0, QWORD PTR __real@c071100000000000 ; -273
mulsd xmm1, QWORD PTR __real@4014000000000000 ; 5
divsd xmm1, QWORD PTR __real@4022000000000000 ; 9
comisd xmm0, xmm1
jbe SHORT $LN1@main
push OFFSET $SG4233 ; ’Error: incorrect temperature!’
call esi ; call printf
add esp, 4
push 0
call DWORD PTR __imp__exit
$LN10@main:
$LN1@main:
sub esp, 8
movsd QWORD PTR [esp], xmm1
push OFFSET $SG4234 ; ’Celsius: %lf’
call esi ; call printf
add esp, 12 ; 0000000cH
; return 0
xor eax, eax
pop esi
add esp, 8
ret 0
$LN8@main:
_main ENDP
```
當然,SIMD在x86下也是可用的,包括這些浮點數的運算。使用他們計算起來也確實方便點,所以微軟編譯器使用了他們。 我們也可以注意到 -273 這個值會很早的被載入XMM0。這個沒問題,因為編譯器并不一定會按照源代碼里面的順序產生代碼。
- 第一章 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