# C語言解釋 - 資料型態 浮點數 ## 程式碼 ###### tags: `c`, `data type`, `float` ```clike= #include <stdio.h> #include <stdlib.h> int main(void) { int A=3; int B=2; int C; double D=1.236, E=9.99, F=0; C=A+B; printf("A+B=%d\t", C); printf("A-B=%d, A=%d\t", A-B, A); printf("A*B=%d\n", A*B); printf("A/B=%f\n", (float)A/B); F = (float)A/B; printf("A/B=%f\n", F); F=D+E; printf("D+E=%f\n", F); F=D-E; printf("D-E=%f, D=%f\n", F, D); F=D*E; printf("D*E=%f\n", F); char qq = 'C'; printf("%c \n", qq); printf("dfaklkeasdfa \n"); printf("%s", "adpnklchladqfaKDkadkasKH \n"); return 0; } ```` ## 標準函式庫 standard library ```clike= #include <stdio.h> #include <stdlib.h> ``` 開頭的`#include` 是前置處理器(preprocessor). 像C語言這種需要編譯的程式語言,從原始程式碼變成執行檔會有以下的流程。 ```sequence 程式碼(.c)->物件檔(.obj): 編譯(compile) 物件檔(.obj)->執行檔(.exe): 連結(link) ``` 從.c的原始碼經過編譯後會產生.obj檔, obj檔經過連結後,就會變成執行檔. 像`#include`, `#define` 這些都是前置處理器,它是在編譯前就會動作。像是`#include`, 在編譯前就會載入你所指定的函式庫. 載入的如果是標準函式庫,就是用角括號包住,如果自訂的函式庫,就是用雙引號,如 ```clike #include <stdio.h>. // 標準函式庫載入 #include "user_define.h" // 使用者自訂的函式庫,載入的方式不同 ``` [cplusplus stdio.h](https://cplusplus.com/reference/cstdio/). 這是標準函式庫 stdio.h 的內容,像後面所用的`printf`就是來自此,除了`printf`外,還有很多很常用的函式,stdio.h它是標準輸出輸入的縮寫,這裡的標準輸入是鍵盤;標準輸出就是螢幕了。 ## Main function ```clike=4 int main() ``` `main()` 是很重要的函式名稱,編譯器會以`main()`為主要函式,其餘函式都是附屬在其底下. ```clike= int main(int argc, char* argv[]) ``` 如果完整寫的話,main function 應該是寫成上述。我們所有寫的程式都是為了跟其他程式溝通,所以要有能接收外部程式傳來的資料。這些資料我們會稱做參數(argument). ```clike= ./sum.exe 0 1 2 3 4 5 15 ``` 假設我們有一支以C語言寫成的sum.exe, 它的後面可以帶入多個參數且幫我們求出總合。那 sum.exe 就是我們編譯後的執行檔,後面帶的數字就叫參數(argument). 總共有六個參數,argc就會為6,而0到5這些數字會以字元陣列的方式儲存在argv. argc(argument count)以及argv(argument vector), 這兩個也是特殊關鍵字,專門替 main function 處理外部參數。 標準的main function,就是上述的兩種寫法。 ## 資料型態 [維基百科 資料型態(C語言)](https://zh.wikipedia.org/zh-tw/数据类型_(C语言)) ```clike=5 int A=3; int B=2; int C; double D=1.236, E=9.99, F=0; C=A+B; printf("A+B=%d\t", C); printf("A-B=%d, A=%d\t", A-B, A); printf("A*B=%d\n", A*B); printf("A/B=%f\n", (float)A/B); F = (float)A/B; ``` 所謂資料型態,就是資料以什麼方式存在記憶體裡,這裡的方式是說資料長度要用多少個位元組(byte)來表示,或是以字元(char)的方式,或是浮點數(float)的方式。 ### 整數 `int` 以整數的型式儲存,通常是指四個位元組大小,可以用`sizeof(int)`來確認當下機台的狀況。 我用lldb debug 工具,把實際記憶體的內容印出來,應該會比較清楚什麼是資料,什麼又是位址。 ```clike= (lldb) l 12 12 printf("A+B=%d\t", C); 13 printf("A-B=%d, A=%d\t", A-B, A); 14 printf("A*B=%d\n", A*B); 15 16 printf("A/B=%f\n", (float)A/B); 17 18 F = (float)A/B; 19 20 printf("A/B=%f\n", F); 21 (lldb) frame variable A B C (int) A = 3 (int) B = 2 (int) C = 5 (lldb) frame variable &A &B &C (int *) &A = 0x000000016fdfeab8 (int *) &B = 0x000000016fdfeab4 (int *) &C = 0x000000016fdfeab0 (lldb) x/8 0x000000016fdfeab0 0x16fdfeab0: 0x00000005 0x00000002 0x00000003 0x00000000 0x16fdfeac0: 0x6fdfec10 0x00000001 0x0001108c 0x00000001 (lldb) ``` * `l 12` 印出12行後程式碼. * `frame varible A B C` 印出變數A, B及C. 我事先有設立中斷點在第11行,且執行到11行,所以A, B, C, D, F 應該都已經存入記憶體內。可以看上述的第13到15行,的確是程式裡所寫內容。 * `frame variable &A &B &C`這裡一樣是印出變數,不過我用了`&A`取出變數A實際記憶體位址,結果就印在17到19行。 * `x/8 0x000000016fdfeab0` 我選擇一個eab0位址,連續印八筆資料。可以稍微對照一下各個變數的位址及資料內容。因為儲存一個int需要四個位元組,所以才會看到開頭這麼多零。 ### 浮點數 看完整數,我們接著看浮點數。浮點數儲存的方式並非像整數一樣那麼容易暸解,程式裡的浮點數都是以[維基百科 IEEE-754](https://zh.wikipedia.org/zh-tw/IEEE_754) ```clike= (lldb) po sizeof(double) 8 (lldb) po sizeof(float) 4 (lldb) frame variable D E F (double) D = 1.236 (double) E = 9.9900000000000002 (double) F = 12.34764 (lldb) frame variable &D &E &F (double *) &D = 0x000000016fdfeaa8 (double *) &E = 0x000000016fdfeaa0 (double *) &F = 0x000000016fdfea98 (lldb) x/8 0x000000016fdfea98 0x16fdfea98: 0xdebd9019 0x4028b1fd 0x47ae147b 0x4023fae1 0x16fdfeaa8: 0xef9db22d 0x3ff3c6a7 0x00000005 0x00000002 ``` 這次我們看浮點數是以什麼方式存在記憶體裡。但首先我們先看double, float各別佔多少空間。從第二跟第五行的結果,在這我這台機器,分別是八個位元組及四個位元組,所以最基本的來講單精準度(float)跟雙精準度(double),就是能表達的位元組差一倍,也就是說雙精準度能夠表示更精準。 [IEEE-754 轉換](https://web.archive.org/web/20070417004305/http://babbage.cs.qc.edu/IEEE-754/Decimal.html) 可以試著玩看看從十進制的小數換成浮點數。 從上面的轉換工具,應該可以知道程式是怎麼表達浮點數的。 總共會轉換成三個欄位。 * sign * exponent * fraction 它並不是一般我們知道的數學上的小數表示,而是經過轉換的。為何需要轉換?目前電腦表示方式主要還是以二進制為主,以二進制的方式是不能完整表達數學上的小數的,所以才有這個轉換方式產生。 可以試著用上面的轉換工具,跟lldb印出來的值做個比較。 ### 字元 ```clike= (lldb) po sizeof(char) 1 (lldb) frame variable qq (char) qq = 'C' (lldb) frame variable &qq (char *) &qq = 0x000000016fdfea97 "C\U00000019\x90\xbd\xde\xfd\xb1(@{\U00000014\xaeG\xe1\xfa#@-\xb2\x9d\xef\xa7\xc6\xf3?\U00000005" (lldb) x/8 0x000000016fdfea97 0x16fdfea97: 0xbd901943 0x28b1fdde 0xae147b40 0x23fae147 0x16fdfeaa7: 0x9db22d40 0xf3c6a7ef 0x0000053f 0x00000200 ``` 一樣用lldb看字元的表示方式,用`sizeof(char)`可以知道字元只需一個位元組大小儲存,儲存的位址放在0xea97,對照來看可以查到0x43這個資料,再去對照 [ASIC](https://zh.wikipedia.org/wiki/ASCII),大寫字母`C`, ASIC code就是0x43.