# C 語言強制轉型 (casting) ```c #include <stdio.h> int main() { unsigned int a = 10; double b = (double)a; printf("a = %u, b = %lf in decimal form\n", a, b); printf("a = %x, b = %x in hex form\n", a, b); } ``` 以上是一個簡單的 C 語言 casting,把無號數轉換成浮點數 ``` a = 10, b = 10.000000 in decimal form a = a, b = 15 in hex form ``` 可以看到儘管表示同一個數字,但是實際上在不同資料型態間二進位表示是完全不同的,這也是為什麼在 C 語言中我們需要透過 Casting 強制轉型才能讓一個數值在不同資料型態間轉換 > 不過你有想過,強制轉型底層是怎麼實做的嗎? ## `char` to `int` 與 `short` to `int` ```clike int main() { char c = 0x30; int b = (int) c; short s = 0x30; int a = (int) s; } ``` ``` push rbp mov rbp, rsp mov BYTE PTR [rbp-1], 48 movsx eax, BYTE PTR [rbp-1] mov DWORD PTR [rbp-8], eax mov WORD PTR [rbp-10], 48 movsx eax, WORD PTR [rbp-10] mov DWORD PTR [rbp-16], eax mov eax, 0 pop rbp ret ``` 1. 把原本只佔 1 BYTE 的`char` 形態用`int` Double WORD (4 BYTE) 的方法取出。 2. 把原本只佔 1 WORD 的`short` 形態用`int` Double WORD (4 BYTE) 的方法取出。 ## `int` to `float` > int 轉換成 float 就比較困難,float 因為遵守 IEEE 754 的規定雖然可以表示小數點但是數值的表示變得複雜 ```clike int main() { int a = -10; float b = (float) a; return 0; } ``` 下面是利用 godbolt 所產生的組合語言 ``` push rbp mov rbp, rsp mov DWORD PTR [rbp-4], -10 cvtsi2ss xmm0, DWORD PTR [rbp-4] movss DWORD PTR [rbp-8], xmm0 mov eax, 0 pop rbp ret ``` cvtsi2ss 即是把`int`轉換成`float`的 x86 指令,這個指令的全名是 > CVTSI2SS — Convert Doubleword Integer to Scalar Single-Precision Floating-Point Value > 也就是用一個指令來 convert。至於實作就看各家晶片製造商的做法。 xmm1 這個暫存器也很有趣,根據[維基百科](https://en.m.wikipedia.org/wiki/Streaming_SIMD_Extensions#Registers)的敘述,xmm1 是在 SSE ( Streaming SIMD Extensions ) 指令集的時候加入的暫存器,有 128 bits 可以進行單指令多資料的操作。 :::info 除了資料型態轉換可能會改變原始資料儲存方式,C 語言中的「型別修飾字」(qualifiers)會不會也會影響呢? ::: ## `int` to `unsigned int` ```c= int main() { int a = -10; unsigned int b = (unsigned int) a; printf("a=%x, b=%x\n", a, b); } ``` 事實上,這邊印出來的 hex 型態都是相同的 0xfffffff6 :::info 其實 `unsigned` 這樣的修飾字並不會影響資料數值,不過在讀取的時候對同一筆資料的解讀方式會不一樣 > C99 規格書裡面應該可以找到 reference,我是找 `type qualifier` 不過符合的量還是太多 [name=陳奕熹] ::: ## `int` and `const int` ```c int main() { int a = 10; const int b = 10; printf("a = %x, &a = %x\n", a, &a); printf("b = %x, &a = %x\n", b, &b); } ``` 十分意外的,這是編譯後的結果 > a = a, &a = 7fffffffdd90 > b = a, &a = 7fffffffdd94 > /* 用 gdb 看 stack pointer 在 7fffffffddb0 */ a, b在二進制時表現相同這點,從前面的 `unsigned` 部份就可以理解到二進制不會被改動 不過令人意外的是,被標記為 `const` 的資料並不會被存到下面 C語言記憶體配置圖中的 `text` 區域 ![](https://i.imgur.com/ds7FJbu.png) ```c int main() { int a = 10; const int b = (const int) a; } ``` ``` main: push rbp mov rbp, rsp mov DWORD PTR [rbp-4], 10 /* 從這邊開始是 line 4 */ mov eax, DWORD PTR [rbp-4] mov DWORD PTR [rbp-8], eax /* line 4 結束 */ mov eax, 0 pop rbp ret ``` 有趣的是,從編譯出來的組合語言可以知道,事實上 ```c int b = a; ``` ```c const int b = (unsigned int) a; ``` 兩者在完全沒做最佳化(-O0)時,編譯的結果就已經一樣了