[分享] Deep C (by Olve Maudal et al.) 心得

看板C_and_CPP作者 (沒有存在感的人)時間9年前 (2016/03/30 04:35), 9年前編輯推噓7(7011)
留言18則, 7人參與, 最新討論串1/1
本文同步分享在:http://gnitnawtw.blogspot.fr/2016/03/deep-c-c.html 為了增進對C語言的認識,我這幾天看了些成大資工"2016年系統軟體課程"分享的資料 以下是我看Olve Maudal 的投影片:Deep C 的心得 (為了了解內容我有參考wiki跟jserv大的C語言講座) 本文只有提到C的部份,C++的部份會另外寫。 (有錯請不吝指正) 投影片連結: http://slideshare.net/olvemaudal/deep-c 1. 指出以下程式碼的結果與缺失 int main() { int a = 42; printf("%d\n", a); } - 需 #include <stdio.h> 以明確宣告printf函式 + 若使用C++編譯器編譯會失敗,因為C++編譯器需要明確(explicit)宣告使用函式。 + 一般的C編譯器在函式未宣告的情況下會自動新增implicit宣告函式。 - 當連結(link)到標準函式庫,編譯器會找到prinf這個函式 - main為int型態卻沒有回傳值(return 0) + C99或更新的版本,main的回傳值為執行的成功(0)與否 + 但是ANSI C或K&R C,在這種情況下回傳值為未定義(undefined) - 有可能是3(因為printf的回傳值為輸出的char數) + 標準C應該要用main(void) (void表示不需要額外參數) + 標準C表示程式碼必須以一個空行做結。 ============================================================ 2.C語言的變數宣告 - static變數和global(全域)變數(被宣告在main外面)被宣告時自動被預設為0, 非static變數(在{}之內時被自動設為auto)宣告時預設值為undefined,這是因為 + "預設變數為0"這個動作是要額外消耗CPU的,而C語言是非常在乎程式效能的, 所以在不需要的情況下不會刻意去做這種消耗CPU的事。 + static跟global變數是被存在Data(有初始值)或BSS(無初始值)內,這兩個區塊 中的變數都是在編譯期就會固定住變數的位址,所以被預設為0不會影響到執行 的效率。此兩種變數的生命期跟程式的執行期一樣長 + 而auto變數是被儲存在stack裏面,其位址並沒有被固定住。此種變數的生命期 只限於宣告位置前後的{}。 - static變數的影響力只限宣告該變數的檔案,global變數的影響力括及其他的obj檔案。 - 但是在C++的情況下,static變數是被預設成"某個預設值"(naive的情況就是0) - 使用malloc/calloc/realloc宣告的某pointer空間,其生命期截止於 free該pointer的時候 ============================================================ 3.預測以下程式碼的結果 #include <stdio.h> void foo(void) { int a; ++a; printf("%d\n", a); } int main(void) { foo(); foo(); foo(); } - a預設值為undefined - 但在除錯模式的情況下,執行期有可能代為執行memset使得預設值為0 - 依本程式來看,a宣告的時候有可能都出現在stack內同樣的address中, 所以本程式的執行結果可能會顯示出3個連續的數列 (ex: 1,2,3 or 0,1,2 or 22,23,24),不過這視編譯最佳化的結果而定。 =================================================================== 4.最佳化參數的影響 預測以下程式碼的結果並解釋原因 #include <stdio.h> void foo(void) { int a; printf("%d\n", a); } void bar(void) { int a=42; } int main(void) { bar(); foo(); } - 沒最佳化的情況會是42 - 有最佳化的可能情況: + 省去bar(),反正沒影響 + foo會變成main內的inline函式(反正沒其他函式會呼叫,這樣節省函式調用時間) + 所以結果可能會變成某個奇怪的數字(undefined) - 平常儘可能用最佳化參數,以發現潛在的問題。 ============================================================== 5.關於sequence point(順序點): 順序點是副作用發生與否的分界點,在順序點左邊的表示已經發生,右邊的還未發生。 在兩個順序點之間變量只能改變一次,不然結果會是undefined。 ex: (i=i++; 在分號前i的值改變兩次,結果會是undefined)。 C/C++裡的順序點其實不多,大致如下: - &&, ||, 或?(三元運算符) ex: (*a++ != 0 && *b++ != 0) -> 會先算出*a++ != 0,再算出*b++ != 0,然後比較&& - 完整表達式(用;當然這有點廢話) 以逗號分開的變數初始化 - ex: int a[3] = {b++,c--,d(101)}; int x=a++, y=a++; 一定是從左到右(好像是從C++11開始?) 函式的進入點 ex: f(i++) + g(j++) + h(k++), 進入函式f/g/h的是增加過後的i/j/k, 不過f/g/h哪個函式先開始呼叫並不在規範內(unspecified behavior)。 - 涉及到I/O ex: printf("Bug %n %d", &a, 11); 在printf %d前會先執行%n(類似fprintf出多少char)的動作(這裡有爭議) ============================================================== 6.關於sizeof - 此為size_t變數,在32bit的機器上通常為unsigned int, 而在64bit的機器上通常為unsigned long - 所以printf的時候有時會出問題,不能都用%u或%lu。 C99以後可以用%zu代表size_t的變數 ============================================================== 7.關於word alignment(對齊):變數大小對齊有助於改善效能 預測以下程式碼的結果並說明原因(假設為64bit機器): #include <stdio.h> Struct X {int a; char b; int c;}; int main(void) { printf("%zd\n", sizeof(int)); printf("%zd\n", sizeof(char)); printf("%zd\n", sizeof(struct X)); } - 若有類似gcc中-fpack-struct的參數去收緊struct大小, 結果會是4,1,9(不做alignment) - 有word alignment的情況下會是4,1,12 - 若是在struct X中多加一個char d; + 如果char d;在int c;後面,可能會變成4,1,16 + 如果char d;在char b;後面,可能會維持4,1,12 + 結論是看最佳化的演算法而定 - 若是在struct X中多加一個char* d; + 以alignment的結果而定,有可能會是4,1,20或4,1,24 - C/C++編譯器並不會以紀錄struct內變數成員的方法去做最佳化 (這點老實說我搞不清楚) ============================================================ 8.C語言的精神: - 相信coder的能力 - 語言本身儘可能設計得簡單扼要 - 只允許單一方法完成一個動作 - 執行起來愈快愈好,就算要讓程式本身大一點 - 維護起來簡單 - 不會阻止coder去做必須要做的事(因為相信coder) ============================================================ C++的部份等我搞懂再寫(我C++算很不熟了....) -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 86.200.224.29 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1459283703.A.6C9.html

03/30 07:25, , 1F
「給你一條夠長的繩子,相信你不會簡簡單單就勒死自己」
03/30 07:25, 1F

03/30 12:10, , 2F
printf(%d\n, a); 第一頁我只看到這個無敵大缺失
03/30 12:10, 2F

03/30 12:36, , 3F
大概跟通古斯卡大爆炸一樣大 相形之下其他微不足道
03/30 12:36, 3F
感謝更正 因為我也是邊看邊查資料邊寫note,難免有缺漏之處,請多包涵! ※ 編輯: wtchen (86.200.224.29), 03/30/2016 15:45:39

03/30 17:59, , 4F
感謝分享
03/30 17:59, 4F

03/30 19:19, , 5F
第二點 其實是在討論 scope/linkage/namespace/life time
03/30 19:19, 5F

03/30 19:21, , 6F
不知道書上有沒有深入探討
03/30 19:21, 6F
如果有人知道哪邊有這方面較好的文章請告訴我, slides本身並沒提到這麼多,有些我是根據wiki跟jserv的C語言講座歸納出來的

03/30 19:23, , 7F
第五點 f/g/h哪個函式先開始呼叫 <- 應該是 unspecified
03/30 19:23, 7F

03/30 19:24, , 8F
behavior 而不是 undefined behavior
03/30 19:24, 8F
感謝指正(昨天邊頭痛邊寫大概腦子壞掉了,心裡想的跟打出來的不一樣)

03/30 19:24, , 9F
而且我不太理解 I/O 那部分跟 sequence point 有什麼關係
03/30 19:24, 9F
這邊我昨天看wiki(slide本身沒這段)的時候也半信半疑,後來寫了個code驗證: wiki上是寫: After the action associated with input/output conversion format specifier. For example, in the expression printf("foo %n %d", &a, 42), there is a sequence point after the %n is evaluated before printing 42. 看起來是說,在印出42之前,會先做%n把a的值更新。 以下是我的測試碼: #include <stdio.h> int main(void) { unsigned int a=0; printf("T1 %n %u\n", &a, a); printf("a=%d\n", a); printf("Tes2 %n %u\n", &a, a); printf("a=%d\n", a); printf("Test3 %u %n\n", a, &a); printf("a=%u\n", a); return 0; } 印出結果: T1 0 a=3 Tes2 3 a=5 Test3 5 a=8 看起來是,不論%n在前面還是在後面,printf都先顯示a原來的值, 後來才執行%n改變a,這好像跟wiki說的不同? ※ 編輯: wtchen (86.200.224.29), 03/30/2016 22:56:41

03/31 01:39, , 10F
03/31 01:39, 10F

03/31 01:40, , 11F
也可以看這個,卡內基美隆分享的一系列安全的規範,
03/31 01:40, 11F

03/31 01:40, , 12F
不只有C還有其他的語言。
03/31 01:40, 12F

03/31 01:47, , 13F
C的精神其實是worse is better,C code一點也不好維護
03/31 01:47, 13F

03/31 01:51, , 14F
C刻意設計得讓compiler容易做,這和「讓語言簡單扼要
03/31 01:51, 14F

03/31 01:51, , 15F
」有相當微妙的差異。
03/31 01:51, 15F
Slide 原文是 Keep the language small and simple Maintain conceptual simplicity (會錯意的話請指正,畢竟在下不是專家) ※ 編輯: wtchen (86.200.224.29), 03/31/2016 01:57:53

03/31 09:51, , 16F
不一定是你會錯意,作者可能也沒區分出兩者的不同
03/31 09:51, 16F

03/31 09:52, , 17F
worse is better 可以看看這篇 https://goo.gl/q7rDZ4
03/31 09:52, 17F
這篇剛剛看完,我的理解是worse-is-better是為了能讓 source code能更容易被不同系統使用(更容易推廣)而做出的妥協。 如果是hardware相關的可以理解,但是若是純演算的情況下....

03/31 11:43, , 18F
C++ Philosophy https://goo.gl/udVmr8
03/31 11:43, 18F
※ 編輯: wtchen (86.200.224.29), 03/31/2016 17:31:56
文章代碼(AID): #1M-kRtR9 (C_and_CPP)