[重要] 發文前務必閱讀:常見問題十三誡

作者: wtchen (沒有存在感的人)   2016-04-18 00:15:48
C 語言新手十三誡(The Thirteen Commandments for Newbie C Programmers)
by Khoguan Phuann
請注意:
(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,
並多多實作練習。
(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並解答了。
(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,
那只是不足為恃的一時僥倖。
(4) 不守十三誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則
引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。
============================================================================
目錄: 2
01. 不可以使用尚未給予適當初值的變數 3
02. 不能存取超過陣列既定範圍的空間 4
03. 不可以提取不知指向何方的指標 5
04. 不要試圖用 char* 去更改一個"字串常數" 8
05. 不能在函式中回傳一個指向區域性自動變數的指標 10
06. 不可以只做 malloc(), 而不做相應的 free() 13
07. 在數值運算、賦值或比較中不可以隨意混用不同型別的數值 14
08. 在一個運算式中,不能對一個基本型態的變數修改其值超過一次以上 16
09. 在 Macro 定義中, 務必為它的參數個別加上括號 19
10. 不可以在 stack 設置過大的變數 21
11. 使用浮點數精確度造成的誤差問題 22
12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞 23
13. 函式內 new 出來的空間記得要讓主程式的指標接住 27
直接輸入數字可跳至該頁碼
01. 你不可以使用尚未給予適當初值的變數
錯誤例子:
int accumulate(int max) /* 從 1 累加到 max,傳回結果 */
{
int sum; /* 未給予初值的區域變數,其內容值是垃圾 */
int num;
for (num = 1; num <= max; num++) { sum += num; }
return sum;
}
正確例子:
int accumulate(int max)
{
int sum = 0; /* 正確的賦予適當的初值 */
int num;
for (num = 1; num <= max; num++) { sum += num; }
return sum;
}
02. 你不可以存取超過陣列既定範圍的空間
錯誤例子:
int str[5];
int i;
for (i = 0 ; i <= 5 ; i++) str[i] = i;
正確例子:
int str[5];
int i;
for (i = 0; i < 5; i++) str[i] = i;
說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面
透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1
C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,
我們要自己來確保不會越界。一旦越界,操作的不再是合法的空間,
將導致無法預期的後果。
03. 你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。
錯誤例子:
char *pc1; /* 未給予初值,不知指向何方 */
char *pc2 = NULL; /* pc2 起始化為 null pointer */
*pc1 = 'a'; /* 將 'a' 寫到不知何方,錯誤 */
*pc2 = 'b'; /* 將 'b' 寫到「位址0」,錯誤 */
正確例子:
char c; /* c 的內容尚未起始化 */
char *pc1 = &c; /* pc1 指向字元變數 c */
*pc1 = 'a'; /* c 的內容變為 'a' */
/* 動態分配 10 個 char(其值未定),並將第一個char的位址賦值給 pc2 */
char *pc2 = (char *) malloc(10);
pc2[0] = 'b'; /* 動態配置來的第 0 個字元,內容變為 'b'
free(pc2);
說明:指標變數必需先指向某個可以合法操作的空間,才能進行操作。
( 使用者記得要檢查 malloc 回傳是否為 NULL,
礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體 )
錯誤例子:
char *name; /* name 尚未指向有效的空間 */
printf("Your name, please: ");
fgets(name,20,stdin); /* 您確定要寫入的那塊空間合法嗎??? */
printf("Hello, %s\n", name);
正確例子:
/* 如果編譯期就能決定字串的最大空間,那就不要宣告成 char* 改用 char[] */
char name[21]; /* 可讀入字串最長 20 個字元,保留一格空間放 '\0' */
printf("Your name, please: ");
fgets(name,20,stdin);
printf("Hello, %s\n", name);
正確例子(2):
/* 若是在執行時期才能決定字串的最大空間,則需利用 malloc() 函式來動態
分配空間 */
size_t length;
char *name;
printf("請輸入字串的最大長度(含null字元): ");
scanf("%u", &length);
name = (char *)malloc(length);
printf("Your name, please: ");
scanf("%s", name);
printf("Hello, %s\n", name);
/* 最後記得 free() 掉 malloc() 所分配的空間 */
free(name);
name = NULL;
04. 你不可以試圖用 char* 去更改一個"字串常數"
錯誤例子:
char* pc = "john"; /* pc 現在指著一個字串常數 */
*pc = 'J'; /* 但是 pc 沒有權利去更改這個常數! */
正確例子:
char pc[] = "john"; /* pc 現在是個合法的陣列,裡面住著字串 john */
/* 也就是 pc[0]='j', pc[1]='o', pc[2]='h',
pc[3]='n', pc[4]='\0' */
*pc = 'J';
pc[2] = 'H';
說明:字串常數的內容是"唯讀"的。您有使用權,但是沒有更改的權利。
若您希望使用可以更改的字串,那您應該將其放在合法空間
錯誤例子:
char *s1 = "Hello, ";
char *s2 = "world!";
/* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串的後面,
造成寫入到程式無權碰觸的記憶體空間 */
strcat(s1, s2);
正確例子(2):
/* s1 宣告成陣列,並保留足夠空間存放後續要附加的內容 */
char s1[20] = "Hello, ";
char *s2 = "world!";
/* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */
strcat(s1, s2);
05. 你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值
[感謝 gocpp 網友提供程式例子]
錯誤例子:
char *getstr(char *name)
{
char buf[30] = "hello, "; /*將字串常數"hello, "的內容複製到buf陣列*/
strcat(buf, name);
return buf;
}
說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
被消滅,因此呼叫端得到的指標所指的字串內容就失效了。
正確例子:
void getstr(char buf[], int buflen, char const *name)
{
char const s[] = "hello, ";
strcpy(buf, s);
strcat(buf, name);
}
正確例子:
int* foo()
{
int* pInteger = (int*) malloc( 10*sizeof(int) );
return pInteger;
}
int main()
{
int* pFromfoo = foo();
}
說明:上例雖然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數,
而是用動態的方式抓取而得,換句話說這塊空間是長在 heap 而非 stack,
又因 heap 空間並不會自動回收,因此這塊空間在離開函式後,依然有效
(但是這個例子可能會因為 programmer 的疏忽,忘記 free 而造成
memory leak)
[針對字串操作,C++提供了更方便安全更直觀的 string class, 能用就盡量用]
正確例子:
#include <string> /* 並非 #include <cstring> */
using std::string;
string getstr(string const &name)
{
return string("hello, ") += name;
}
06. 你不可以只做 malloc(), 而不做相應的 free(). 否則會造成記憶體漏失
但若不是用 malloc() 所得到的記憶體,則不可以 free()。已經 free()了
所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可
以再被 free(),也不可以提取(dereference)這個指標。
[C++] 你不可以只做 new, 而不做相應的 delete
注:new 與 delete 對應,new[] 與 delete[] 對應,不可混用
切記,做了幾次 new,就必須做幾次 delete
小技巧: 可在 delete 之後將指標指到 NULL,由於 delete 本身會先做檢查,
因此可以避免掉多次 delete 的錯誤
正確例子:
int *ptr = new int(99);
delete ptr;
ptr = NULL;
delete ptr; /* delete 只會處理指向非 NULL 的指標 */
07. 你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算
的結果,其範圍是否會超出變數的型別
錯誤例子:
unsigned int sum = 2000000000 + 2000000000; /* 超出 int 存放範圍 */
unsigned int sum = (unsigned int) (2000000000 + 2000000000);
double f = 10 / 3;
正確例子:
/* 全部都用 unsigned int, 注意數字後面的 u, 大寫 U 也成 */
unsigned int sum = 2000000000u + 2000000000u;
/* 或是用顯式的轉型 */
unsigned int sum = (unsigned int) 2000000000 + 2000000000;
double f = 10.0 / 3.0;
錯誤例子:
unsigned int a = 0;
int b[10];
for(int i = 9 ; i >= a ; i
作者: manoeuvre   2015-01-01 14:49:00
可以不要用動畫嗎?
作者: nowar100 (拋磚引玉)   2015-01-10 20:46:00
看來我以前的動畫排版造成不少人的困擾 真抱歉 QQ
作者: kevin770111 (牛牛)   2015-03-03 22:35:00
動畫好難用 By 初學者... = =a
作者: adrianshum (Alien)   2015-03-07 11:24:00
選擇畫面的排版很糟糕, 請全部靠左吧...
作者: nowar100 (拋磚引玉)   2015-03-19 08:35:00
全部靠左的話,就會有空白換行不均的排版問題了 :(
作者: apiod ( )   2015-04-02 22:33:00
可以不要用動畫嗎 =.=
作者: yonderknight (Joe_Black)   2015-04-10 02:33:00
推動畫,雖然主選單有點亂
作者: BlazarArc (Midnight Sun)   2015-04-15 15:55:00
目錄那邊要多空行翻頁才會對
作者: CP64 (( ̄▽ ̄#)﹏﹏)   2015-04-16 01:19:00
拿掉動畫之後我覺得可以改成在後面標頁數 ' -')畢竟 PTT 有支援在文章頁面輸入數字跳至第幾頁 ' -')
作者: wtchen (沒有存在感的人)   2015-04-16 01:46:00
....板工不會用,有強者可以代勞嗎?
作者: poopop   2016-05-01 22:33:00
推,太有用了!
作者: NitroRider (Firedupandreadytoserve )   2016-05-09 16:06:00
剛開始寫程式就連犯5誡以上 根本悲劇
作者: afes8863er (iNoyoka)   2016-06-11 13:41:00
好一個當頭棒喝=一個完美的開始
作者: wawi2 (@@)   2016-06-22 00:14:00
第13點的最後一個例子是不是錯了? int (&array)[10]??
作者: uefang (雞~雞~雞~雞~)   2016-06-26 15:09:00
可以不要用動話嗎?
作者: loveme00835 (髮箍)   2016-09-22 11:13:00
推一個唷~ 變動畫了XDD
作者: xatier (一切重來就好了...)   2016-09-22 11:32:00
昨天晚上就看到變成動畫了 只是沒人推就不敢推下去XD
作者: VictorTom (鬼翼&娃娃魚)   2016-09-22 14:16:00
推....:)
作者: hilorrk (Cary)   2016-09-24 20:21:00
改版了XD
作者: cosmosQQ ( )   2016-09-26 11:00:00
第8戒 int i = 7; int j = ++i + i++; 蠻多公司有這考題printf("i=%d, j=%d \n", i, j); => i=9, j=16一般推論答案無誤, 但就與第8戒違反了@@ 該如何解....
作者: nowar100 (拋磚引玉)   2016-09-26 13:06:00
問題不存在 不應該出現的就是不應該 就算大家約定成俗討論它也是沒什麼意義的
作者: snoopy0907   2016-09-28 20:33:00
推~真的很常犯...話說其實我比較希望有下載版XD
作者: storm654321 (P助)   2016-10-03 08:54:00
十誡~~~ 引以為戒
作者: coldstars   2016-10-10 19:47:00
有時間不是應該要專精自己的領域嗎?
作者: holishing   2016-11-10 18:52:00
什麼時候變13誡了!?
作者: df405102 (恩)   2016-11-16 22:22:00
有些看不太懂....
作者: nowar100 (拋磚引玉)   2016-11-16 22:25:00
樓上哪裡看不懂 可以發文問 版上很熱心的 :)
作者: linesa (八倍速自耕農)   2016-12-10 18:46:00
大推!! 神文!!
作者: antelope01 (羚羊)   2016-12-21 08:01:00
受益匪淺
作者: lucky1lk (賭到沒錢的人)   2016-04-19 07:44:00
有版主 有推倒
作者: yvb   2016-04-21 22:44:00
我每頁不是24行時, 頁數就會錯誤... 可否加上行數? (###.)
作者: wtchen (沒有存在感的人)   2016-04-21 22:51:00
可能要等下次改版(加延伸閱讀部份),不用急不會太久
作者: mythnc (迷小心)   2016-04-29 12:59:00
終於把動畫改掉了~可喜可賀
作者: red0210 (My Name Is Red)   2016-04-30 13:28:00
頁數錯誤是什麼情況?可以說得更清楚一點嗎
作者: yvb   2016-05-07 01:13:00
比方我目前視窗最大化, Rows 42 Columns 168;在這篇文章的第一頁, 下方的瀏覽狀態即顯示:瀏覽 第 1/19 頁 ( 8%) 目前顯示: 第 01~40 行按頁數 12 就已跳到 第10誡 了.
作者: red0210 (My Name Is Red)   2016-05-10 19:21:00
原來如此,不過為何會每頁不是24行…
作者: yvb   2016-05-10 20:33:00
PuTTY Configuration => Window => When window is resized:Change the number of rows and columns用習慣很多列以後, 就回不去了 :P

Links booklink

Contact Us: admin [ a t ] ucptt.com