# 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)時,編譯的結果就已經一樣了