工作好幾年了,一直用的都是C。自認為指針應(yīng)該很熟悉了。然而,前段時間我對二維指針和二維數(shù)組的一個混用,并且我們項目組的一個大牛(博士畢業(yè),工作10+年)在review我的代碼也沒發(fā)現(xiàn)問題,導(dǎo)致代碼上線后出現(xiàn)一個異常。我才覺得我對指針只是學(xué)廢了。找了一些指針和數(shù)組的博客資料,記錄一下。希望下次不會再犯類似的錯誤。
引子
首先看一段代碼:
?
void test(int *p){ } int main(){ int arr[]= {30, 450,14,5}; test(arr); return 0;}
?
毫無疑問,上面這段代碼是運行OK的。因為C語言標(biāo)準(zhǔn)中有以下規(guī)則:在函數(shù)參數(shù)的聲明中,數(shù)組名被編譯器當(dāng)作指向該數(shù)組第一個元素的指針。
第一種錯誤
那下面這段代碼,會正確運行嗎:
?
#include void test(int **p){ }int main(){ int arr[]={30,450,14,5}; test(&arr); return 0;}
?
可能有的同學(xué)認為這段代碼是正確的,因為數(shù)組作為函數(shù)參數(shù)時退化成一個指針,那么我對數(shù)組進行取址,這樣&arr就是一個二維指針了,所以可以作為函數(shù)test的入?yún)ⅰ?/p>
這個理由貌似有道理,但很遺憾,這段代碼會報編譯錯誤:
?
main.c:3:17: note: expected 'int **' but argument is of type 'int (*)[4]' void test(int **p) ~~~~~~^
?
這個錯誤是說,test函數(shù)期望的入?yún)㈩愋褪莍nt **。但是實際傳入的參數(shù)類型是 int (*)[4],即實際傳入的類型是指向數(shù)組的指針。
C語言標(biāo)準(zhǔn)中是定義了:在函數(shù)參數(shù)的聲明中,數(shù)組名被編譯器當(dāng)作指向該數(shù)組第一個元素的指針。但是你不能因為數(shù)組在函數(shù)參數(shù)中當(dāng)成一個指針,你對數(shù)組名取地址&arr就認為它的類型就是指向指針的指針(int **),這樣以為是錯的,因為不具備這樣的傳遞性。C語言規(guī)范中只規(guī)定了數(shù)組名作為函數(shù)的入?yún)r會被當(dāng)做一個指針,但是并沒有規(guī)定對數(shù)組取地址會被當(dāng)成指向指針的指針。
根據(jù)上面代碼的報錯信息提示,將不能編譯通過的代碼片段作一個小的修改,就會編譯通過了:
?
#include #include void test(int (*p)[4]){ (*p)[2] = 10;} int main(){ int arr[] = {30, 450, 14, 5}; test(&arr); printf("%d ", arr[2]); return 0;}
?
輸出: 10
但其實上面的這種用法非常的怪。至少我在平時的工作中沒遇到過這樣的寫法。這個函數(shù)的意圖是改變數(shù)組的某個元素的內(nèi)容。那下面的這種寫法更加清晰和常見:
?
void test(int *p){ p[2] = 10;} int main(){ int arr[] = {30, 450, 14, 5}; test(arr); printf("%d ", arr[2]); return 0;}
?
第二種錯誤
這種錯誤就是我之前在工作中犯的,從而導(dǎo)致出現(xiàn)異常的。
假如有一段代碼:
?
#include #include void foo(int **arr, int m, int n){ int i,j; for (i = 0; i < m; i++) { for (j = 0; j < n; j++) { printf("%d ", arr[i][j]); } printf(" "); }} int **alloc_2d(int m, int n){ int **arr = malloc(m * sizeof(*arr)); int i; for (i = 0; i < m; i++) { arr[i] = malloc(n * sizeof(**arr)); } return arr;} int main(){ int **joe = alloc_2d(2, 3); joe[0][0] = 1; joe[0][1] = 2; joe[0][2] = 3; joe[1][0] = 4; joe[1][1] = 5; joe[1][2] = 6; return 0;}
?
上面的代碼至此還是能正常工作的。
現(xiàn)在,我想用函數(shù)foo來打印一個二維的數(shù)組,那我能用下面的這段代碼嗎?
?
int moe[2][3];moe[0][0] = 1;moe[0][1] = 2;moe[0][2] = 3;moe[1][0] = 4;moe[1][1] = 5;moe[1][2] = 6; foo(moe, 2, 3);
?
很遺憾,這段代碼同樣會報編譯錯誤:
?
ain.c:42:9: warning: passing argument 1 of 'foo' from incompatible pointer type [-Wincompatible-pointer-types] foo(moe, 2, 3); ^~~main.c:3:16: note: expected 'int **' but argument is of type 'int (*)[3]' void foo(int **arr, int m, int n)
?
將二維數(shù)組作為入?yún)魅雈oo函數(shù)的原因可能是錯誤的認為二維數(shù)組就是二維指針。為什么會這么想呢? 我以前的一個思考過程是這樣的:一維數(shù)組作為函數(shù)參數(shù)的時候,編譯器會把數(shù)組名當(dāng)作指向該數(shù)組第一個元素的指針。所以我想當(dāng)然的以為:既然一維數(shù)組和一維指針在函數(shù)參數(shù)中等價,那二維數(shù)組應(yīng)該就等價于二維指針。
但是很遺憾,二維數(shù)組作為函數(shù)參數(shù)并等價于二維指針。因為數(shù)組作為函數(shù)參數(shù)時轉(zhuǎn)換為指針沒有傳遞性。也就是說你不能認為一維數(shù)組和一維指針作為函數(shù)參數(shù)等價,就認為二維數(shù)組和二維指針就等價了。在C語言中沒有這樣的傳遞性。
其實仔細想想,也是很容易明白的。二維數(shù)組其實就是一個數(shù)組的數(shù)組(即它是一個一維數(shù)組,只不過它的每個元素又都是一個一維數(shù)組)。當(dāng)二維數(shù)組作為函數(shù)入?yún)r,比如 int a[3][4]; 它的第一維數(shù)組會被轉(zhuǎn)換成指針,相當(dāng)于是傳入了一個指向數(shù)組的指針。即作為函數(shù)參數(shù), int a[3][4]和 int (*p)[4]等價。那 int (*p)[4]和 int **pp等價嗎?肯定不等價, p指針指向類型是 int [4],而pp指向的類型是int *。
那有什么辦法能讓上面的foo函數(shù)編譯通過呢?
最直接的方式是將二維數(shù)組作為函數(shù)的參數(shù)傳入:
?
void foo(int arr[2][3], int m, int n){}
?
事實上,上面的第一維的參數(shù)可以去掉:
?
void foo(int arr[][3], int m, int n){}
?
上面也分析了,二維數(shù)組作為函數(shù)入?yún)⒑?int (*p)[3]等價,所以也可以寫成下面的形式:
?
void foo(int (*p)[3], int m, int n){}
?
總結(jié)
上面說了2維數(shù)組作為函數(shù)參數(shù)的情況,那3維數(shù)組呢?
和二維一樣:首先將類似三維數(shù)組arr[2][3][4]傳入到 int ***類型的函數(shù)參數(shù)是錯誤的。但是可以將這個三維數(shù)組傳入到參數(shù)類型為 int arr[][3][4]或者 int (*arr)[3][4]的函數(shù)中。
"數(shù)組名被改寫成一個指針參數(shù)"規(guī)則并不是遞歸定義的(沒有傳遞性)。數(shù)組的數(shù)組會被改寫為"數(shù)組的指針",而不是"指針的指針"。
你之所以能在main()函數(shù)中看到char **argv這樣的參數(shù),是因為argv是個指針數(shù)組(即 char *argv[])。這個表達式被編譯器改寫為指向數(shù)組第一個元素的指針,也就是一個指向指針的指針。如果argv參數(shù)事實上被聲明為一個數(shù)組的數(shù)組(也就是char argv[10][5]), 它將被編譯器改寫為 char (*argv)[15],也就是一個字符數(shù)組的指針,而不是char **argv。
審核編輯:湯梓紅
?
評論
查看更多