# 20160923 Sysprog ## 課堂測驗 Q1 以下程式作用為何? ``` int p(int i, int N){ return (i<N && printf("%d\n", i )&& !p(i +1, N))\ || printf("%d\n",i); } int main(){ p(1,5); return 0; } ``` * `&&` 前後的敘述對調,是否影響輸出? * 從 `i < N && printf("%d\n", i)` 變更為 `printf("%d\n", i) && (i < N)` 的話,程式輸出為何? A: 當 `i<N` 成立時 會執行 `printf("%d\n", i)` 直到 `i = N` (`!p(i + 1, N)`形成遞迴)停止 執行 `||` 後的 `printf` 當 `i = N` 時停止 call function 並且執行 `||` 後的 `printf` 回傳不為零的 integer 利用 gdb bt 可以查看到堆疊追蹤 ![](https://i.imgur.com/L3tx3Dp.png) 印出 : ``` 1 2 3 4 5 4 3 2 1 ``` 對調是會影響,讓原本`i=N`時先印出數字再判斷判別式,不成立時讓 `||` 後再印出一次 印出: ``` 1 2 3 4 5 5 4 3 2 1 ``` ### 延伸題: Q : 需要聲明「有效數值範圍」,比方說參數 i 與 N ? A : i < N Q : 使用遞迴,stack depth 的限制為何 ? A : unix stack size default : 8MBytes windows stack size default : 1MBytes 利用 `$ ulimit -s` 可以查出 stack size 在電腦上查到是 8192kB ,以這題為例在遞迴時會將變數暫存再stack,每呼叫一次會存入回傳位址 8bytes, int i, N 變數共 8bytes, printf()*2 8 bytes, p回傳回來值int 4bytes ,總共 32 bytes 8MB = 8,388,608 bytes 8,388,608 / 32 =262,144 次 >一開始算的時候發現很多因素未考量只算出 20bytes 算出來值在程式去驗證,發現再 261899 次時出現 segmentation fault ,通過 8388608 / x = 261899 算出大概是32bytes bytes很多沒考量到,所以開gdb disass 開始探討 ``` Dump of assembler code for function p: 0x0000000000400526 <+0>: push %rbp 0x0000000000400527 <+1>: mov %rsp,%rbp 0x000000000040052a <+4>: sub $0x10,%rsp 0x000000000040052e <+8>: mov %edi,-0x4(%rbp) 0x0000000000400531 <+11>: mov %esi,-0x8(%rbp) 0x0000000000400534 <+14>: mov -0x4(%rbp),%eax 0x0000000000400537 <+17>: mov %eax,%esi 0x0000000000400539 <+19>: mov $0x400634,%edi 0x000000000040053e <+24>: mov $0x0,%eax 0x0000000000400543 <+29>: callq 0x400400 <printf@plt> 0x0000000000400548 <+34>: test %eax,%eax 0x000000000040054a <+36>: je 0x40056a <p+68> 0x000000000040054c <+38>: mov -0x4(%rbp),%eax 0x000000000040054f <+41>: cmp -0x8(%rbp),%eax 0x0000000000400552 <+44>: jge 0x40056a <p+68> 0x0000000000400554 <+46>: mov -0x4(%rbp),%eax 0x0000000000400557 <+49>: lea 0x1(%rax),%edx 0x000000000040055a <+52>: mov -0x8(%rbp),%eax 0x000000000040055d <+55>: mov %eax,%esi 0x000000000040055f <+57>: mov %edx,%edi 0x0000000000400561 <+59>: callq 0x400526 <p> 0x0000000000400566 <+64>: test %eax,%eax 0x0000000000400568 <+66>: je 0x400582 <p+92> 0x000000000040056a <+68>: mov -0x4(%rbp),%eax 0x000000000040056d <+71>: mov %eax,%esi 0x000000000040056f <+73>: mov $0x400634,%edi 0x0000000000400574 <+78>: mov $0x0,%eax 0x0000000000400579 <+83>: callq 0x400400 <printf@plt> 0x000000000040057e <+88>: test %eax,%eax 0x0000000000400580 <+90>: je 0x400589 <p+99> 0x0000000000400582 <+92>: mov $0x1,%eax 0x0000000000400587 <+97>: jmp 0x40058e <p+104> 0x0000000000400589 <+99>: mov $0x0,%eax 0x000000000040058e <+104>: leaveq 0x000000000040058f <+105>: retq End of assembler dump. ``` 0 < N - i < 262,144 在計算中還有未考量因素,在用程式去驗證時,實際上還會更小。 ## Q2: 考慮以下 swap 程式: ```C void swap(int *a, int *b) { int t; t = *a; *a = *b; *b = t; } ``` 能否避免使用區域變數 t?又,可否改用 bit-wise operator 來改寫? A: 算術運算-利用兩個相差距離做運算 但如果遇到一個很大的整數與一個負數(足夠讓他超過最大整數)兩個相減時,會產生益位 ``` *a = *a - *b; //兩數相差 *b = *a + *b; //相加得出*b *a = *b - *a; //相減得出*a ``` 邏輯運算-運用邏輯運算查看出兩個不同相差的位元 真值表 ![](https://i.imgur.com/v4P4oIs.png) ``` *a = *a XOR *b //算出相差位元 *b = *a XOR *b //與相差位元做XOR得出*a *a = *a XOR *b //與相差位元做XOR得出*b ``` * 參考資料 [wiki](https://zh.wikipedia.org/wiki/%E9%80%BB%E8%BE%91%E5%BC%82%E6%88%96) 邏輯運算的好處是,可避免前述算術會遇到的 integer overflow,而且不限定位元數。 ## Q3: 用 C 語言實做 `char *reverse(char *s)` 來反轉 NULL 結尾的字串,限定 in-place 與遞迴 參考實作 (都還可以大幅改進,只是列出來給大家看) * [Day2](https://embedded2015.hackpad.com/GGI4IhFdLUW) **C-style string reverse (遞迴 + in-place)** * 先想想這個版本可用嗎? (以及對應的問題) ```C void reverse (char *s) { if (*s == ’\0’) return; reverse (s+1); printf ("%c", *s); } ``` ```C char reverse(char *s){ return (*s == '\0') || reverse(s+1) && printf("%c",*s); } ``` A : 一開始想法:兩兩交換,但可能字串太長回圈會耗太多時間 ```c= char *reverse(char *s){ static int time = -1; static int length = -1; int first = 1; if (time == -1){ time = strlen(s) - 1; length = strlen(s); first = time; } //想法:兩兩交換 while(first--){ if(*(s+ (length-time)) == '\0' ){ return s; } *s = (s ^ *(s+1); *(s+1) =*(s+1) ^ *s; *s = *s ^ *(s+1); reverse(s+1); if(first) time = first; } return s; } int main(){ char s[] ="123456789"; printf("%s \n", reverse(s)); return 0 ; } ``` 經過老師延伸問題提問,發現有 Reentrancy 問題,程式碼還有很大的改進空間,可以再優化,詳細看延伸題。 >> 以上實做不符合需求,應該是輸入一個 pointer to character,回傳一個 pointer to character,這兩者的起始位址可以不同,也能相同,後者稱為 in-place。注意看題目要求! [name=jserv] >>> 好的老師,修正好了謝謝 [name=HuangWenChen] ### 延伸題: Q : 是否存在 Reentrancy 問題? A : 是 * 參考資料 * [Reentrancy wiki]( https://en.wikipedia.org/wiki/Reentrancy_(computing)) Reentrancy 問題是否能重複去使用。 Reentrancy 規則: 1. Reentrant code may not hold any static (or global) non-constant data. 2. Reentrant code may not modify its own code. 3. Reentrant code may not call non-reentrant computer programs or routines. 而我再上面code運用到 static 變數再次使用會有問題。 優化改進 ```c= char *reverse(char *s){ static int time = -1; static int length = -1; int first = 1; if (time == -1){ time = strlen(s) - 1; length = strlen(s); first = time; } //想法:兩兩交換 while(first--){ if(*(s+ (length-time)) == '\0' ) return s; //swap *s ^= *(s+1) ^= *s ^= *(s+1); reverse(s+1); if(first) time = first; } if(time == 1) time = -1; return s; } ``` 時間複雜度 O(n) 僅能在 single thread 重複使用 multithread 會有共用變數問題