看板 C_and_CPP 關於我們 聯絡資訊
本文同步分享在: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
Schottky: 「給你一條夠長的繩子,相信你不會簡簡單單就勒死自己」 03/30 07:25
MOONRAKER: printf(%d\n, a); 第一頁我只看到這個無敵大缺失 03/30 12:10
MOONRAKER: 大概跟通古斯卡大爆炸一樣大 相形之下其他微不足道 03/30 12:36
感謝更正 因為我也是邊看邊查資料邊寫note,難免有缺漏之處,請多包涵! ※ 編輯: wtchen (86.200.224.29), 03/30/2016 15:45:39
descent: 感謝分享 03/30 17:59
FRAXIS: 第二點 其實是在討論 scope/linkage/namespace/life time 03/30 19:19
FRAXIS: 不知道書上有沒有深入探討 03/30 19:21
如果有人知道哪邊有這方面較好的文章請告訴我, slides本身並沒提到這麼多,有些我是根據wiki跟jserv的C語言講座歸納出來的
FRAXIS: 第五點 f/g/h哪個函式先開始呼叫 <- 應該是 unspecified 03/30 19:23
FRAXIS: behavior 而不是 undefined behavior 03/30 19:24
感謝指正(昨天邊頭痛邊寫大概腦子壞掉了,心裡想的跟打出來的不一樣)
FRAXIS: 而且我不太理解 I/O 那部分跟 sequence point 有什麼關係 03/30 19:24
這邊我昨天看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
WorkForFree: https://goo.gl/idBJ55 03/31 01:39
WorkForFree: 也可以看這個,卡內基美隆分享的一系列安全的規範, 03/31 01:40
WorkForFree: 不只有C還有其他的語言。 03/31 01:40
littleshan: C的精神其實是worse is better,C code一點也不好維護 03/31 01:47
littleshan: C刻意設計得讓compiler容易做,這和「讓語言簡單扼要 03/31 01:51
littleshan: 」有相當微妙的差異。 03/31 01:51
Slide 原文是 Keep the language small and simple Maintain conceptual simplicity (會錯意的話請指正,畢竟在下不是專家) ※ 編輯: wtchen (86.200.224.29), 03/31/2016 01:57:53
littleshan: 不一定是你會錯意,作者可能也沒區分出兩者的不同 03/31 09:51
littleshan: worse is better 可以看看這篇 https://goo.gl/q7rDZ4 03/31 09:52
這篇剛剛看完,我的理解是worse-is-better是為了能讓 source code能更容易被不同系統使用(更容易推廣)而做出的妥協。 如果是hardware相關的可以理解,但是若是純演算的情況下....
Caesar08: C++ Philosophy https://goo.gl/udVmr8 03/31 11:43
※ 編輯: wtchen (86.200.224.29), 03/31/2016 17:31:56