# 2018q3 Homework1 contributed by < [LiuJuiHung](https://github.com/LiuJuiHung) > :::danger 你的提問呢?回顧你過去開發的程式,難道沒想到概念上有衝突或者激發出更多想法嗎? :notes: jserv ::: ## 你所不知道的 C 語言:開發工具和規格標準 ### 授權差異規範 **1. musl libc:** * 介紹: * musl ,一種 C 標準函式庫,主要使用於以 Linux 內核為主的作業系統上,目標為嵌入式系統與行動裝置,採用 MIT 許可證釋出。作者為瑞奇·費爾克 * [musl-Introduction](https://www.musl-libc.org/intro.html) 裡的 [POSIX](https://zh.wikipedia.org/wiki/POSIX)(Portable Operating System Interface) * musl libc 使用 [MIT License 授權條款](https://zh.wikipedia.org/wiki/MIT%E8%A8%B1%E5%8F%AF%E8%AD%89) * 1. 被授權人權利: * 被授權人有權利使用、複製、修改、合併、出版發行、散布、再授權和/或販售軟體及軟體的副本,及授予被供應人同等權利,惟服從以下義務 * 2. 被授權人義務: * 在軟體和軟體的所有副本中都必須包含以上版權聲明和本許可聲明 * 3. 其他重要特性: * 此授權條款並非屬 copyleft 的自由軟體授權條款,允許在自由及開放原始碼軟體或非自由軟體(proprietary software)所使用 * MIT的內容可依照程式著作權者的需求更改內容。此亦為 MIT 與 BSD(The BSD license, 3-clause BSD license)本質上不同處 * MIT 授權條款可與其他授權條款並存。另外,MIT 條款也是自由軟體基金會(FSF)所認可的自由軟體授權條款,與 GPL 相容 **2. glibc:** * 介紹: * 全名 GNU C Library ,是一個C語言的資料庫,按照 lgpl Licence 發佈的 * 已預先編譯好,已二進制代碼存在 Linux 系統中 * Glibc 最初是自由軟體基金會(FSF)為其 GNU 操作系统所寫,但目前最主要的应用是配合 Linux 内核,成为 GNU/Linux 操作系统一个重要的组成部分 * [glibc](https://zh.wikipedia.org/wiki/GNU_C%E5%87%BD%E5%BC%8F%E5%BA%AB) * lgpl Lincense: * 最初於1991年發布,原本被稱爲 GNU Library General Public License * 2.1版於1999年在修訂後發布。它被重命名爲 GNU Lesser General Public License * LGPL 的第3版於2007年發布 * LGPL 有一特點是 LGPL 軟體可以被轉換成 GPL * 主要是面向使用 C 語言以及類 C 語言 * [lgpl](https://zh.wikipedia.org/wiki/GNU%E5%AE%BD%E9%80%9A%E7%94%A8%E5%85%AC%E5%85%B1%E8%AE%B8%E5%8F%AF%E8%AF%81) --- ### C 語言和 C++ 分道揚鑣 1. 自 C99 起 C 語言有支援 Designate Initializer ,而 C++ 則沒有支援 ```C struct point p = {.y=yvalue, .x=xvalue}; ``` 2. C++ 則有 Constructor * 以 C 語言開發者較在意程式執行順序, **Constructor 在實作上可能會發生在main之前**,可能會在平台移植上有困難或與假設的結果有落差 --- ### 第一個 C 語言編譯器是怎麼編寫的 1. Dennis Ritchie 是 C 語言重要開發者、Unix 共同開發者之一 2. plan9 貝爾實驗室九號計劃,再看到 Unix 成功後重新開發的一個分散式作業系統作為後續 Unix 的版本 3. plan9: Everything is a file 4. 先創造一個 C 語言最的基本功能的子集合為 C0 語言(最簡單,比 C 語言簡單很多),之後由 C0 開發出 C1 編譯器,直到現在的 C 語言編譯器 5. [complier bootstraping 介紹](https://en.wikipedia.org/wiki/Bootstrapping_(compilers)): * *Many compilers for many programming languages are bootstrapping* 6. [第一個 C 語言編譯器是怎樣編寫的?](http://blog.jobbole.com/94311/) --- ### C 語言規格介紹 1. ISO/IEC 9899 (簡稱 " C99 ") 2. ISO( International Organization for Standardization 國際標準化組織) 3. IEC(International Electrotechnical Commission 國際電工委員會) 4. [ISO/IEC 646](https://en.wikipedia.org/wiki/ISO/IEC_646) * This article is about a character encoding standard 5. *in C, everything is a representation(unsigned char[sizeof(TYPE)])* 6. [C99規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) 7. " & " 念法: * " Bit wise and ",有涉及到Bit wise operation * " address of ",涉及指標操作, >C99 標準 [6.5.3.2] Address and indirection operators 8. `*` 念法: * 應念成 "value of" 或 "dereference",`*` 是 pointer type,表示所指向的值。 > C99 標準 [6.5.3.2] The operand of the unary * operator shall have pointer type. 9. C 語言沒有真正的 array ,最終都會轉換成 *" 同等功能的指標 "* 10. " lvalue " * 在 C99 規格書裡稱 lvalue 為 " locator value " * 是一個 C 語言和 C++ 分道揚鑣的因素,因為兩者解釋的 " lvalue "不一樣 --- ## 你所不知道的 C 語言:指標篇 ### 我所知道的基本指標用法 #### 基本練習 ```C //基本練習 #include<stdio.h> int main() { int a = 2; int *ptr = &a; printf("the value of a:%d\n", a); printf("the address of a: %p\n", &a); printf("the value of ptr:%p\n", ptr); printf("the value of *ptr:%d\n", *ptr); printf("the address of ptr:%p\n", &ptr); } ``` Result ``` the value of a:2 the address of a: 0x7ffda042c73c the value of ptr:0x7ffda042c73c the value of *ptr:2 the address of ptr:0x7ffda042c740 ``` ### 規格書 1. 在 C 語言規格書中沒有" double pointer ",正確唸法" a pointer to a pointer " 2. 資料->連續的記憶體 3. C 語言只有 call by value 4. C 語言 Types * C 語言規格書中,Type有三種: * Object types * Function types * Incomplete types >不是一個物件 >可宣告,但需要定義大小 >不能從 inpomlete type 建立一個 instance -> 也就是不能真正的建立一個物件 > 5. pointer type 沒有提供完整的運算,能做'+'、'-',但不能做'*'、'/' 6. Object in C ,是有明確大小 --- ### void * 之謎 1. void 在最早的 C 語言是不存在的,直到 C89 才建立 2. 若函式沒有標住返回型態,一律變成 int 回傳 ```C=1 main() { } ``` Result ``` void.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int] main() ^ ``` #### 為何要設計 void 型態? 1. 會導致無從驗證 function prototype 和實際的使用狀況 ```C=1 main() { } short int a() { return main(); } ``` :::warning int 佔用空間會比 short int 大,例如,把一個大的 資料放到一個比較小的地方,有些資訊會不見 ::: 2. 涉及到指標的操作,指標是用來處理記憶體,若沒有很明確的說明要指向哪個位址,此時會導致認知上的錯誤 * 無法對 void * 直接做數值操作,必須透過 explicit (顯式) 或強制轉型,避免危險的指標操作 * 無法直接對 void * 做數值操作 ```C=1 //無法直接對 void * 做數值操作 int main() { char *x=0; void *y=0; char a=*x; char b=*y; } ``` Result :::warning void.c: In function ‘main’: void.c:6:12: warning: dereferencing ‘void *’ pointer char b=*y; void.c:6:12: error: void value not ignored as it ought to be ::: * 強制將 void * 轉型 ```C=1 ///強制將 void * 轉型 int main() { char *x=0; void *y=0; char a=*x; char b=*(char *)y; } ``` Result :::success 此時 y 就有明確的佔用空間,可編譯成功 ::: --- ### 沒有「雙指標」,只有「指標的指標」 * 為何不能叫「雙指標」? * 「雙」有「對稱」且「獨立」的含意 * 指標是「從屬」關係,所以不能叫「雙指標」 * C 語言中,**everything is a value** ,所以函式呼叫**只有「call by value」** * a pointer of a pointer 是個常見用來改變「傳入變數原始數值」的技巧 * 指標的指標簡單練習: * 比較以下兩段程式 * 目標:改變ptrA的內含值 ```C=1 #include<stdio.h> int B=2; void func(int *p){p=&B;} int main() { int A=1, C=3; int *ptrA=&A; func(ptrA); printf("*ptrA=%d\n",*ptrA); return 0; } ``` Result :::info *ptrA=1 ::: main 中的 ptrA 內含值沒有改變。我們可透過「指標的指標」來改寫,如下: ```C=1 #include<stdio.h> int B=2; void func(int **p){*p=&B;} int main() { int A=1, C=3; int *ptrA=&A; func(&ptrA); printf("*ptrA=%d\n",*ptrA); return 0; } ``` Result :::info *ptrA=2 *ptrA 數值從1變成2,ptrA 指向的物件也改變了 ::: * 解析 :::warning func(ptrA) 是帶入一個複本,所以 func 執行 p=&B 時,並不會真正更改到ptrA的值 ::: * a pointer to a pointer 可以延長變數的 lifetime * pointer 困難的地方不是用幾顆 ' * ',而是難在使用情境 * 搜尋 open source 內,哪裡使用了 " *** "(a pointer to a pointer to a pointer) --- ### Pointer vs. Array * in declaration * extern 型態不能變更為 pointer 型式,如 `extren char x[]=> 不能變更為 pointer 型式` * definition/statement 不能更換為 pointer 型式,如`char x[10]=>因為 array 有明確的大小,所以就是一個物件,跟指標就無關` * parameter of function,如 `func(char x[])=>可變更為pointer 型式=> func(char *x)` * in expersion * array 和 pointer 可互換 * 簡單互換實作 ```C=1 #include<stdio.h> int main() { int x[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; printf("%d %d %d %d\n", x[4], *(x + 4), *(4 + x), 4[x]); } ``` Result :::success 4 4 4 4 證明 array 和 pointer 式可以互換的 ::: * C 語言中,沒有真正的陣列/矩陣 * Array subscripting * [C99](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) [6.5.2.1] * C 語言提供多微陣列,實際上只有一微陣列的資料存取 * 對應到線性記憶體 * 在 C 語言中 >`x[i]` 會轉成 `(*((x)+(i)))` * array subscripting 編譯時期只做兩件事: >* 得知 size >* Obtain a pointer to element 0 * 前兩者以外的操作,都透過 pointer >* array subscripting => syntax sugar #### 學習使用 GDB Array declaration: ```clike int a[3]; struct { double v[3]; double length; } b[17]; int calendar[12][31]; int main() {} ``` 編譯時加入參數: ``` $ gcc -o filename -Og -g filename.c ``` * `g` : 編入除錯資訊(GDB 除錯使用) * `Og` : 針對 debugger 做優化 把編譯完成的執行檔載入到 gdb: ``` $ gdb -q filename # filename is your Execution file ``` 中斷點設置: ``` # set the breakpoints to main (gdb) b main # run (gdb) r ``` `p` 為印出: ``` (gdb) p sizeof(calendar) $1 = 1488 ``` :::danger 注意: sizeof() 是 operator 不是 function ::: --- 實驗1: sizeof(calendar) = 1488 1488 怎麼來的?代表什麼? sizeof(b) = 544 ``` (gdb) peint 1488 / 12 / 31 $2 = 4 (gdb) p sizeof(int) $3 = 4 (gdb) p sizeof(b) $4 = 544 (gdb) print 544 / 17 $5 = 32 (gdb) print 544 / 17 / 4 $6 = 8 (gdb) p sizeof(double) $7 = 8 ``` :::success 由以上實驗可知 `4` 就是 `int` 的大小 `8` 就是 `double` 的大小 ::: --- 分析型態: Command:`whatis` ``` (gdb) whatis calendar type = int [12][31] ``` 觀察記憶體內容: ``` (gdb) x/4 b 0x601060 <b>: 0 0 0 0 (gdb) p b $11 = {{v = {0, 0, 0}, length = 0} <repeats 17 times>} ``` --- 實驗2: ``` (gdb) p &b $8 = (struct {...} (*)[17]) 0x601060 <b> (gdb) p &b+1 $9 = (struct {...} (*)[17]) 0x601280 <a> (gdb) p sizeof(b[0]) $12 = 32 (gdb) print 32*17 $13 = 544 ``` * 為何 `&b` 和 `&b+1` 差了544的大小? 由上可知,b[0] 的大小為 32,b 總共有 17 個 elements ,所以 `+1` 取決於 b 所屬的型態 ``` (gdb) p &b $16 = (struct {...} (*)[17]) 0x601060 <b> (gdb) p &b+1 $17 = (struct {...} (*)[17]) 0x601280 <a> (gdb) p &b[0]+1 $18 = (struct {...} *) 0x601080 <b+32> ``` :::success 結論: `&b+1` 是加一整塊,也就是加 17 個 elements `&b[0]+1` 是加一個 element 的大小 所以完全取決於前面的 operate dereference 後所屬的型態 ::: --- 命令 `p` 不僅能 print ,也可以變更記憶體內容 ``` (gdb) x/4 b 0x601060 <b>: 0 0 0 0 (gdb) p (&b[0])->v={1,2,3} $28 = {1, 2, 3} (gdb) x/4 b 0x601060 <b>: 0 1072693248 0 1073741824 (gdb) p b[0] $37 = {v = {1, 2, 3}, length = 0} ``` * `x/4 b` 查看是一堆數字? * 因為 v 本身型態是 `double` * 在 C 的 float 是根據 [IEEE754](https://zh.wikipedia.org/wiki/IEEE_754) 強制轉型: ``` (gdb) whatis (&b[0])->v[0] type = double (gdb) p sizeof(&b[0])->v[0] $29 = 8 (gdb) p &(&b[0])->v[0] $30 = (double *) 0x601060 <b> (gdb) p (int *) &(&b[0])->v[0] $31 = (int *) 0x601060 <b> (gdb) p *(int *) &(&b[0])->v[0] $32 = 0 (gdb) p *(double *) &(&b[0])->v[0] $33 = 1 ``` * 為何 int 測試出來是 0 呢? ``` (gdb) p sizeof(int) $18 = 4 ``` 因為 int 長度為 4,所以只取出 v[0] 開頭的 4 bytes,內容就是 0 延伸閱讀: [「你所不知道的 C 語言」系列講座]( https://hackmd.io/s/HJpiYaZfl) - 數值系統篇 --- ### int main(int argc, char *argv[ ], char *envp[ ]) 的奧祕 * C99 規格書 [5.1.2.2.1] * [argv](https://www.teach.cs.toronto.edu//~ajr/209/notes/argv.html) * 實驗: ```clike #include <stdio.h> int main(int argc, char (*argv)[0]) { puts(((char **) argv)[0]); return 0; } ``` 執行結果 ``` $ ./arg ./arg ``` * 使用 GDB 觀察 ``` (gdb) b main Breakpoint 1 at 0x400535: file a.c, line 4. (gdb) r Starting program: /home/hung/Desktop/practice/arg Breakpoint 1, main (argc=1, argv=0x7fffffffde98) at a.c:4 warning: Source file is more recent than executable. 4 puts(((char **)argv)[0]); (gdb) print *((char **) argv) $1 = 0x7fffffffe246 "/home/hung/Desktop/practice/arg" ``` 觀察 argv ``` (gdb) x/4s (char **)argv 0x7fffffffde98: "F\342\377\377\377\177" 0x7fffffffde9f: "" 0x7fffffffdea0: "" 0x7fffffffdea1: "" ``` 觀察 argv[0] ``` (gdb) x/4s ((char **)argv)[0] 0x7fffffffe246: "/home/hung/Desktop/practice/arg" 0x7fffffffe266: "LC_PAPER=lzh_TW" 0x7fffffffe276: "XDG_VTNR=7" 0x7fffffffe281: "XDG_SESSION_ID=c2" ``` 發現 `argv` 原來是`環境變數`