法院文件翻譯C/C++ 說話新手十三誡(The Thirteen Commandments for Newbie C/C++ Programmers)
by Khoguan Phuann
請留意:
(1) 本篇旨在提示新手,避免初學常犯的錯誤(其實老手也常犯:-Q)翻譯
但不克不及代替完全的進修,請自己好好研讀一兩本 C 說話的好書,
並多多實作練習。
(2) 強烈建議新手先看過此文再提問,翻譯公司的問題很可能此文已提出並
解答了翻譯
(3) 以下所舉的毛病例子如果在翻譯公司的電腦上印出和准確例子不異的效果,
那只是不足為恃的一時僥倖翻譯
(4) 不守十三誡者,輕則履行成果的輸出數據毛病,或是程式當掉,重則
引爆核彈、毀滅地球(若是你的 C 程式是用來節制核彈發射器的話)翻譯
=============================================================
目次: (頁碼/行號) 2/24
01. 不可以利用還沒有賜與適當初值的變數 3/46
02. 不能存取跨越陣列既定規模的空間 5/90
03. 不可以提取不知指向何方的指標 7/134
04. 不要試圖用 char* 去更改一個"字串常數" 12/244
05. 不能在函式中回傳一個指向區域性主動變數的指標 16/332
06. 不成以只做 malloc()翻譯社 而不做響應的 free() 19/398
07. 在數值運算、賦值或對照中不可以隨便混用分歧型其余數值 21/442
08. ++i/i++/--i/i--/f(&i)哪個先履行跟挨次有關 24/508
09. 慎用Macro 27/574
10. 不要在 stack 設置過大的變數以免堆疊溢位(stack overflow) 32/684
11. 使用浮點數精確度釀成的誤差問題 35/750
12. 不要料想二維陣列可以用 pointer to pointer 來傳遞 36/772
13. 函式內 new 出來的空間記得要讓主程式的指標接住 40/860
直接輸入數字可跳至該頁碼
或用:指令輸入行號
01. 你不可以使用尚未賜與恰當初值的變數
毛病例子:
int accumulate(int max) /* 從 1 累加到 max,傳回效果 */
{
int sum; /* 未賜與初值的區域變數,其內容值是垃圾 */
for (int num = 1; num <= max; num++) { sum += num; }
return sum;
}
准確例子:
int accumulate(int max)
{
int sum = 0; /* 准確的賦予恰當的初值 */
for (int num = 1; num <= max; num++) { sum += num; }
return sum;
}
備註:
按照 C Standard,具有靜態貯存期(static storage duration)的變數,
例如 全域變數(global variable)或帶有 static 修飾符者等,
如果沒有顯式初始化的話,按照分歧的資料型態予以進行以下初始化:
若變數為算術型別 (int , double , ...) 時,初始化為零或正零。
若變數為指標型別 (int*, double*翻譯社 ...) 時,初始化為 null 指標。
若變數為複合型別 (struct, double _Complex翻譯社 ...) 時,遞迴初始化所有成員。
若變數為聯合型別 (union) 時,只有此中的第一個成員會被遞迴初始化。
(以上感激Hazukashiine板友斧正)
(但是有些MCU 編譯器可能不睬會這個劃定,所以還是請養成設定初值的好習慣)
彌補資料:
- 精髓區z->5->1->1->1
- C11 Standard 5.1.2, 6.2.4, 6.7.9
02. 你弗成以存取超過陣列既定規模的空間
毛病例子:
int str[5];
for (int i = 0 ; i <= 5 ; i++) str[i] = i;
准確例子:
int str[5];
for (int i = 0; i < 5; i++) str[i] = i;
申明:宣佈陣列時,所給的陣列元素個數值若是是 N翻譯社 那麼華頓翻譯公司們在後面
透過 [索引值] 存取其元素時,所能利用的索引值範圍是從 0 到 N-1
C/C++ 為了執行效力,其實不會自動查抄陣列索引值是不是跨越陣列邊界,
我們要本身來確保不會越界。一旦越界,操作的不再是正當的空間,
將導致沒法預期的後果翻譯
備註:
C++11以後可以用Range-based for loop提取array、
vector(或是其他有供應正確.begin()和.end()的class)內的元素
可以確保提取的元素必然落在正確範圍內。
例:
// vector
std::vector<int> v = {0, 1翻譯社 2, 3, 4, 5};
for(const int &i : v) // access by const reference
std::cout << i << ' ';
std::cout << '
';
// array
int a[] = {0, 1, 2翻譯社 3, 4翻譯社 5};
for(int n: a) // the initializer may be an array
std::cout << n << ' ';
std::cout << '
';
補充資料:
http://en.cppreference.com/w/cpp/language/range-for
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
", name);
准確例子:
/* 若是編譯期就可以決議字串的最大空間,那就不要宣佈成 char* 改用 char[] */
char name[21]; /* 可讀入字串最長 20 個字元,保存一格空間放 '\0' */
printf("Your name, please: ");
fgets(name翻譯社20,stdin);
printf("Hello, %s
"翻譯社 name);
准確例子(2):
若是在執行時期才能決意字串的最大空間,C供應兩種作法:
a. 哄騙 malloc() 函式來動態分派空間,用malloc宣佈的陣列會被存在heap
須注重:若是宣佈較大陣列,要確認malloc的回傳值是否為NULL
size_t length;
printf("請輸入字串的最大長度(含null字元): ");
scanf("%u", &length);
name = (char *)malloc(length);
if (name) { // name != NULL
printf("您輸入的是 %u
"翻譯社 length);
} else { // name == NULL
puts("輸入值太大或系統已無足夠空間");
}
/* 最跋文得 free() 掉 malloc() 所分派的空間 */
free(name);
name = NULL; //(註1)
b. C99開始可以使用variable-length array (VLA)
須注意:
- 因為VLA是被寄存在stack裡,利用前要確認array size不克不及太大
- 不是每個compiler都支援VLA(註2)
- C++ Standard不支援(固然有些compiler支援)
float read_and_process(int n)
{
float vals[n];
for (int i = 0; i < n; i++)
vals[i] = read_val();
return process(vals翻譯社 n);
}
准確例子(3):
C++的使用者也有兩種作法:
a. std::vector (不管你的陣列巨細會不會變都可用)
std::vector<int> v1;
v1.resize(10); // 從新設定vector size
b. C++11今後,若是確定陣列巨細不會變,可以用std::array
須注重:一般利用下(存在stack)一樣要確認array size不克不及太大
std::array<int, 5> a = { 1, 2翻譯社 3 }; // a[0]~a[2] = 1翻譯社2,3; a[3]之後為0;
a[a.size() - 1] = 5; // a[4] = 0;
備註:
註1. C++的利用者,C++03或之前請用0代替NULL,C++11最先請改用nullptr
註2. gcc和clang支援VLA,Visual C++不支援
增補資料:
http://www.cplusplus.com/reference/vector/vector/resize/
04. 你不可以試圖用 char* 去更改一個"字串常數"
試圖去更改字串常數(string literal)的了局會是undefined behavior。
毛病例子:
char* pc = "john"; /* pc 現在指著一個字串常數 */
*pc = 'J'; /* undefined behaviour,了局沒法預測*/
pc = "jane"; /* 正當,pc指到在此外位址的另一個字串常數*/
/* 然則"john"這個字串照舊存在原來的處所不會消逝*/
因為char* pc = "john"這個動作會新增一個內含元素為"john\0"的static char[5],
然後pc會指向這個static char的位址(平常是唯讀)翻譯
若是試圖存取這個static char[],Standard並沒有界說成績為何翻譯
pc = "jane" 這個動作會把 pc 指到另一個沒在用的位址然後新增一個
內含元素為"jane\0"的static char[5]。
可是之前阿誰字串 "john
" 仍是留在原地沒有消失。
每每編譯器的作法是把字串常數放在一塊read only(.rdata)的區域內,
此區域巨細是有限的,所以假如翻譯公司反複把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);
C++對於字串常數的嚴厲界說為const char* 或 const char[]。
但是由於要相容C,char* 也是答應的寫法(不建議就是)翻譯
不外,在C++試圖更改字串常數(要先const_cast)一樣是undefined behavior。
const char* pc = "Hello";
char* p = const_cast<char*>(pc);
p[0] = 'M'; // undefined behaviour
備註:
由於不加const容易造成混合,
建議不論是C還是C++一概用 const char* 定義字串常數。
增補資料:
http://en.cppreference.com/w/c/language/string_literal
http://en.cppreference.com/w/cpp/language/string_literal
字串函數相幹:#1IOXeMHX
undefined behavior : 精髓區 z -> 3 -> 3 -> 23
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. [C]你不成以只做 malloc()翻譯社 而不做相應的 free(). 不然會造成記憶體漏失
但若不是用 malloc() 所獲得的記憶體,則不可以 free()。已 free()了
所指記憶體的指標,在它指向另外一塊有效的動態分派得來的空間之前,弗成
以再被 free(),也不行以提取(dereference)這個指標。
小技能: 可在 free 以後將指標指到 NULL,free不會對空指標作用翻譯
例:
int *p = malloc(sizeof(int));
free(p);
p = NULL;
free(p); // free不會對空指標有作用
[C++] 你不行以只做 new, 而不做相應的 delete (除unique_ptr以外)
註:new 與 delete 對應,new[] 與 delete[] 對應,
弗成與malloc/free混用(結果不行展望)
切記,做了幾回 new,就必需做幾次 delete
小技能: 可在 delete 以後將指標指到0或nullptr(C++11開始),
由於 delete 自己會先做搜檢,是以可以避免掉屢次 delete 的毛病
正確例子:
int *ptr = new int(99);
delete ptr;
ptr = nullptr;
delete ptr; /* delete 只會處置懲罰指向非 NULL 的指標 */
備註:
C++11後新增智能指標(smart pointer): unique_ptr
當unique_ptr所指物件消逝時,會主動釋放其記憶體,不需要delete。
例:
#include <memory> // 含unique_ptr的標頭檔
std::unique_ptr<int> p1(new int(5));
彌補資料:
http://en.cppreference.com/w/cpp/memory/unique_ptr
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--) { b[i] = 0; }
申明:由於 int 與 unsigned 共同運算的時刻,會轉換 int 為 unsigned,
是以迴圈前提永久知足,與預期行為不符
毛病例子: (感謝 sekya 網友提供)
unsigned char a = 0x80; /* no problem */
char b = 0x80; /* implementation-defined result */
if( b == 0x80 ) { /* 紛歧定恒真 */
printf( "b ok
" );
}
申明:語言並未劃定 char 天生為 unsigned 或 signed,是以將 0x80 放入
char 型態的變數,將會視各家編譯器分歧作法而有分歧效果
毛病例子(以下假定為在32bit機器上執行):
#include <math.h>
long a = -2147483648 ; // 2147483648 = 2 的 31 次方
while (labs(a)>0){ // labs(-2147483648)<0 有可能産生
++a;
}
申明:若是翻譯公司去看C99/C11 Standard,你會發現long
變數的最大/最小值為(被define在limits.h)
LONG_MIN -2147483647 // compiler實作時最小值不成大於 -(2147483648-1)
LONG_MAX 2147483647 // compiler實作時最小值不成小於 (2147483648-1)
不過由於32bit能顯示的規模就是2**32種,所以一般16/32bit功課系統會把
LONG_MIN多減去1,也就是int 的顯示局限為(-LONG_MAX - 1) ~ LONG_MAX。
(64bit的功課系統long多為8 bytes,但是照舊符合Standard要求的最小局限)
當程式跑到labs(-2147483648)>0時,由於2147483648大於LONG_MAX,
Standard告知華頓翻譯公司們,當labs的成效沒法被long有限的局限暗示,
編譯器會怎麼幹就看他歡快(undefined behavior)。
(不只long,其他如int、long long等以此類推)
彌補資料:
- C11 Standard 5.2.4.2.1翻譯社 7.22.6.1
- https://www.fefe.de/intof.html
08. ++i/i++/--i/i--/f(&i)哪一個先履行跟遞次有關
++i/i++ 和--i/i-- 的問題幾近每月都邑呈現,所以特別強調。
當一段程式碼中,某個變數的值用某種方式被改變一次以上,
例如 ++x/--x/x++/x--/function(&x)(能改變x的函式)
- 若是Standard沒有特別去界說某段敘述中哪個部分必需被先履行,
那成績會是undefined behavior(結果未知)。
- 如果Standard有出格去界說履行按次,那成績就憑據履行挨次決定。
C/C++均正確的例子:
if (--a || ++a) {} // ||左邊先計較,如果左側為1右邊就不會算
if (++i && f(&i)) {} // &&左邊先計較,如果左邊為0右側就不會算
a = (*p++) ? (*p++) : 0 ; // 問號左側先計較
int j = (++i, i++); // 這裡的逗號為運算子,表示依序計較
C/C++均錯誤的例子:
int j = ++i + i++; // undefined behavior,Standard沒界說+號哪邊先執行
x = x++; // undefined behavior, Standard沒界說=號哪邊先履行
printf( "%d %d %d", I++, f(&I), I++ ); // undefined behavior, 緣由同上
foo(i++翻譯社 i++); // undefined behavior,這裡的逗號是用來分隔引入參數的
// 分隔符(separator)而非運算子,Standard沒定義哪邊先履行
在C與C++03錯誤然則在C++11入手下手(但不包括C)准確的例子:
C++11中,++i/--i為左值(lvalue),i++/i--為右值(rvalue)。
左值可以被assign value給它,右值則不行翻譯
而在C中,++i/--i/i++/i--都是右值。
所以以下的code在C++會准確,C則否。
++++++++++phew ; // C++11會把它解釋為++(++(++(++(++phew))));
i = v[++i]; // ++i會先完成
i = ++i + 1; // ++i會先完成
在C++17起頭(但不包括C)才准確的例子:
cout << i << i++; // 先左後右
a[i] = i++; // i++先做
a[x++] = --x; // 先處置懲罰--x,再處置a[x++] (loveflames彌補)
補充資料
- Undefined behavior and sequence points
http://stackoverflow.com/questions/4176328/undefined-behavior-and-
sequence-points)
- C11 Standard 6.5.13-17,Annex C
- Sequence poit
https://en.wikipedia.org/wiki/Sequence_point
- Order of evaluation
http://en.cppreference.com/w/cpp/language/eval_order
09. 慎用macro(#define)
Macro是個像鐵鎚一樣好用又危險的東西:
用得好可以釘釘子,用欠好可以把釘子打彎、敲到你手指或被抓去吃槍彈。
因為macro 界說出的「偽函式」有以下瑕玷:
(1) debug會變得複雜。
(2) 沒法遞迴呼叫。
(3) 沒法用 & 加在 macro name 之前,獲得函式位址。
(4) 沒有namespace。
(5) 可能會致使奇怪的side effect或其他沒法展望的問題。
所以,利用macro前,請先確認以上的弱點是不是會影響你的程式運行翻譯
替換方案:enum(界說整數),const T(界說常數),inline function(界說函式)
C++的template(界說可用分歧type參數的函式),
或C++11開始的匿名函式(Lambda function)與constexpr T(編譯期常數)
以下就針對macro的瑕玷做申明:
(1) debug會變得複雜翻譯
編譯器不克不及對macro自己做語法檢查,只能檢查預處置懲罰(preprocess)後的結果翻譯
(2) 無法遞迴呼喚。
根據C standard 6.10.3.4,
假如某macro的定義裡裏面含有跟此macro名稱一樣的的字串,
該字串將不會被預處理。
所以:
#define pr(n) ((n==1)? 1 : pr(n-1))
cout<< pr(5) <<endl;
預處置事後會釀成:
cout<< ((5==1)? 1 : pr(5 -1)) <<endl; // pr沒有界說,編譯會失足
(3) 沒法用 & 加在 macro name 之前,獲得函式位址。
因為他不是函式,所以你也不成以把函式指標套用在macro上。
(4) 沒有namespace。
毛病例子:
#define begin() x = 0
for (std::vector<int>::iterator it = myvector.begin();
it != myvector.end(); ++it) // begin是std的保存字
std::cout << ' ' << *it;
改良方法:macro名稱一概用大寫,如BEGIN()
(5) 可能會導致希奇的side effect或其他沒法預測的問題翻譯
錯誤例子:
#include <stdio.h>
#define SQUARE(x) (x * x)
int main()
{
printf("%d
", SQUARE(10-5)); // 預處置後釀成SQUARE(10-5*10-5)
return 0;
}
准確例子:在 Macro 定義中翻譯社 務必為它的參數個別加上括號
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main()
{
printf("%d
", SQUARE(10-5));
return 0;
}
不外碰到以下有side effect的例子就算加了括號也沒用翻譯
毛病例子: (感激 yaca 網友提供)
#define MACRO(x) (((x) * (x)) - ((x) * (x)))
int main()
{
int x = 3;
printf("%d
", MACRO(++x)); // 有side effect
return 0;
}
彌補資料:
- http://stackoverflow.com/questions/14041453/why-are-preprocessor-
macros-evil-and-what-are-the-alternatives
- http://stackoverflow.com/questions/12447557/can-we-have-recursive-macros
- C11 Standard 6.10.3.4
- http://en.cppreference.com/w/cpp/language/lambda
10. 不要在 stack 設置過大的變數以避免堆疊溢位(stack overflow)
由於編譯器會自行決定 stack 的上限,某些預設是數 KB 或數十KB,當變數所需的空
間過大時,很輕易造成 stack overflow,程式亦隨之當掉(segmentation fault)。
可能造成堆疊溢位的緣由包孕遞迴太屢次(多為程式設計缺點),
或是在 stack 設置過大的變數。
毛病例子:
int array[10000000]; // 在stack宣佈過大陣列
std::array<int, 10000000> myarray; //在stack宣告過大std::array
正確例子:
C:
int *array = (int*) malloc( 10000000*sizeof(int) );
C++:
std::vector<int> v;
v.resize(10000000);
申明:建議將利用空間較大的變數用malloc/new設置裝備擺設在 heap 上,由於此時 stack
上只需設置裝備擺設一個 int* 的空間指到在heap的該變數,可避免 stack overflow。
使用 heap 時,固然全部 process 可用的空間是有限的,但採用動態抓取
的方式,new 沒法配置時會丟出 std::bad_alloc 例外,malloc 沒法設置裝備擺設
時會回傳 null(註2),不會影響到正常使用下的程式功能
備註:
註1. 利用 heap 時,全部 process 可用的空間一樣是有限的,若是需要頻仍地
malloc / free 或 new / delete 較大的空間,需注重避免造成記憶體破裂
(memory fragmentation)翻譯
註2. 由於Linux利用overcommit機制治理記憶體,malloc即使在記憶體不足時
依然會回傳非NULL的address,一樣情形在Windows/Mac OS則會回傳NULL
(感激 LiloHuang 彌補)
彌補資料:
- https://zh.wikipedia.org/wiki/%E5%A0%86%E7%96%8A%E6%BA%A2%E4%BD%8D
- http://stackoverflow.com/questions/3770457/what-is-memory-fragmentation
- http://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/
overcommit跟malloc:
- http://goo.gl/V9krbB
- http://goo.gl/5tCLQc
11. 利用浮點數萬萬要注重切確度所釀成的誤差問題
按照 IEEE 754 的規範,又電腦中是用有限的二進位貯存數字,是以常有可
能因為切確度而造成誤差,例如加減乘除,等號大小判定,分派律等數學上
常用到的操作,很有可能是以而出錯(不成立)
更詳細的說明可以參考精華區 z-8-11
或參考冼鏡光先生所揭橥的一文 "利用浮點數最最基本的觀念"
http://blog.dcview.com/article.php?a=VmhQNVY%2BCzo%3D
12. 不要料想二維陣列可以用 pointer to pointer 來傳遞
(感激 loveme00835 legnaleurc 版友的協助)
起首必需有個觀念,C 語言中陣列是沒法直接拿來傳遞的!
不外這時候候會有人跳出來辯駁:
void pass1DArray( int array[] );
int a[10];
pass1DArray( a ); /* 可以正當編譯,並且履行結果正確!! */
事實上,編譯器會這麼對待
void pass1DArray( int *array );
int a[10];
pass1DArray( &a[0] );
我們可以趁便看出來,array 變數本身可以 decay 成記憶體開端的位置
因此我們可以 int *p = a; 這類體例,拿指標去接陣列翻譯
也因為上述的例子,許多人以為那二維陣列是否是也能夠改成 int **
錯誤例子:
void pass2DArray( int **array );
int a[5][10];
pass2DArray( a );
/* 這時候候編譯器就會報錯啦 */
/* expected ‘int **’ but argument is of type ‘int (*)[10]’*/
在一維陣列中,指標的移動操作,會剛好籠蓋到陣列的局限
例如,宣佈了一個 a[10],那我可以把 a 當做指標來操作 *a 至 *(a+9)
因此我們可以獲得一個概念,在操作的時辰,可以 decay 成指標來利用
也就是華頓翻譯公司可以把一個陣列當做一個指標來使用 (again翻譯社 陣列!=指標)
可是多維陣列中,無法如此利用,事實上這也很直觀,試圖拿一個
pointer to pointer to int 來操作一個 int 二維陣列,這是不公道的!
儘管我們沒法將二維陣列直接 decay 成兩個指標,然則華頓翻譯公司們可以換個角度想,
二維陣列可以算作 "外層大的一維陣列,每維內層各又包括著一維陣列"
如果想通了這一點,我們可以仿制之前的法則,
把外層大的一維陣列 decay 成指標,該指標指向內層的一維陣列
void pass2DArray( int (*array) [10] ); // array 是個指標,指向 int [10]
int a[5][10];
pass2DArray( a );
這時候就很好理解了,函數 pass2DArray 內的 array[0] 會代表什麼呢?
謎底是它代表著 a[0] 外層的那一維陣列,裡面包括著內層 [0]~[9]
也是以 array[0][2] 就會對應到 a[0][2],array[4][9] 對應到 a[4][9]
結論就是,只有最外層的那一維陣列可以 decay 成指標,其他維陣列都要
明白的指出陣列巨細,這樣多維陣列的傳遞就不會有問題了
也因為方才的例子,我們可以清晰的知道在傳遞陣列時,實際行為是在傳遞
指標,也是以若是華頓翻譯公司們想用 sizeof 來求得陣列元素個數,那是不可行的
毛病例子:
void print1DArraySize( int* arr ) {
printf("%u", sizeof(arr)/sizeof(arr[0])); /* sizeof(arr) 只是 */
} /* 一個指標的大小 */
受此限制,我們必需手動傳入巨細
void print1DArraySize( int* arr, size_t arrSize );
C++ 提供 reference 的機制,使得我們不需再這麼麻煩,
可以直接傳遞陣列的 reference 給函數,巨細也可以直接求出
正確例子:
void print1DArraySize( int (&array)[10] ) { // 傳遞 reference
cout << sizeof(array) / sizeof(int); // 准確獲得陣列元素個數
}
13. 函式內 new 出來的空間記得要讓主程式的指標接住
對指標不熟習的使用者會以為以下的程式碼是相符預期的
void newArray(int* local翻譯社 int size) {
local = (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
newArray(ptr, 10);
}
接著就會找了好久的 bug,最後仍然搞不懂為什麼 ptr 沒有指向方才拿到的正當空間
讓我們再回顧一次,而且用圖透露表現 (感謝Hazukashiine板友供應圖解)
┌────┐ ┌────┐ ┌────┐ ┌────┐
Heap │ │ │ │ │ 新配置 │ │ 已泄露 │
│ │ │ │ │ 的空間 <─┐ │ 的空間 │
│ │ │ │ │(allocd)│ │ │(leaked)│
│ │ │ │ ├────┤ │ ├────┤
│ │ │ │ │ : │ │ │ │
│ │ │ │ │ : │ │ │ : │
│ │ ├────┤ ├────┤ │ │ : │
│ │ │ local ├─┐ │ local ├─┘ │ │
├────┤ ├────┤ │ ├────┤ ├────┤
Stack │ ptr ├─┐ │ ptr ├─┤ │ ptr ├─┐ │ ptr ├─┐
└────┘ ╧ └────┘ ╧ └────┘ ╧ └────┘ ╧
未初始化 函式呼喚 設置裝備擺設空間 函式返回
int *ptr; local = ptr; local = malloc();
用圖看應當一切就都明了了,我也不需冗言解釋
或許有人會想問,指標不是傳址嗎?
正確來說,指標也是傳值,只不過該值是一個位址 (ex: 0xfefefefe)
local 接到了 ptr 指向的阿誰位置,接著函式內 local 要到了新的位置
可是 ptr 指向的位置仍是沒變的,是以離開函式後就似乎事什麼都沒産生
( 嚴格說起來還發生了 memory leak )
以下是一種解決設施
int* createNewArray(int size) {
return (int*) malloc( size * sizeof(int) );
}
int main() {
int* ptr;
ptr = createNewArray(10);
}
改成如許亦可 ( 為何用 int** 就能夠?想一想他會傳什麼曩昔給local )
void createNewArray(int** local, int size) {
*local = (int*) malloc( size * sizeof(int) );
}
int main() {
int *ptr;
createNewArray(&ptr翻譯社 10);
}
假如是 C++,別忘了可以善用 Reference
void newArray(int*& local, int size) {
local = new int[size];
}
跋文:從「古時刻」流傳下來一篇文章
"The Ten Commandments for C Programmers"(Annotated Edition)
by Henry Spencer
http://www.lysator.liu.se/c/ten-commandments.html
一方面它不是針對 C 的初學者,一方面它特地仿照中古英文
聖經的用語,寫得文謅謅翻譯所以我而今別的寫了這篇,但願
能涵蓋最主要的觀念和初學甚至熟手在行最易犯的錯誤。
作者:潘科元(Khoguan Phuann) (c)2005. 感激 ptt.cc BBS 的 C_and_CPP
看板眾多網友供給珍貴定見及程式實例。
nowar100 屢次加以點竄整理,擴充至 13 項,而且製作成動畫版翻譯
wtchen 應板友要求移除動畫並根據C/C++標準修改內容(Ver.2016)
如發現 Bug 請推文回報,感謝您
本文引用自: https://www.ptt.cc/bbs/C_and_CPP/M.1465304337.A.9F2.html有關各國語文翻譯公證的問題歡迎諮詢華頓翻譯公司02-77260932