作者wtchen (沒有存在感的人)
看板C_and_CPP
標題[分享] Deep C (by Olve Maudal et al.) 心得
時間Wed Mar 30 04:34:56 2016
本文同步分享在:
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: 也可以看這個,卡內基美隆分享的一系列安全的規範, 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
這篇剛剛看完,我的理解是worse-is-better是為了能讓
source code能更容易被不同系統使用(更容易推廣)而做出的妥協。
如果是hardware相關的可以理解,但是若是純演算的情況下....
※ 編輯: wtchen (86.200.224.29), 03/31/2016 17:31:56