# 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` 原來是`環境變數`