指標及一維陣列
在不少的書籍,我們都可以看到可以把陣列看成指標,這不完全正確,但在實作上也不能說完全不正確
本質上陣列名稱代表的是一個位址,而指標代表的是位址的位址,因此在宣告上是不相等的,例如你在某一個地方宣告了int a[10],而在其他地方想extern進來,但如果使用的是extern *a,那會compile error,因為位址和位址的位址並不一樣
再來如果a[10]真的和*a是一樣的,那sizeof(a)就會應該是4
那為什麼我們在實際使用上結果會是一樣的呢呢?下面這個例子都會印出20
int a[] = {10, 20, 30};
printf("%d", a[1]);
printf("%d", *(a + 1));
那是因為陣列在compiler階段都會被轉成指標,當看到a[1]的時候就從a的位址加上1個integer的size,如果看到*(a + 1)那行為會一樣
差別只在如果compiler知道a是陣列那會忽略’*’和’&’也就是不會作dereference,這樣也可以解釋為什麼下面這情況compiler認為是合法的
int b[3] = {10, 20, 30};
b[99] = 40;
那在什麼狀況下會完全相等呢?只有在傳陣列參數到函式的時候,以下三種情況compiler編出來會一模一樣,事實上在傳陣列到函式裡面時,真正傳的是指標並不是整份陣列複製過去
func(int *verctor) {...};
func(int verctor[]) {...};
func(int verctor[10]) {...};
試著想一下以下這段程式碼會印出什麼
#include <stdio.h>
char ga[2] = {'a', 'b'};
void func_in_vec(char ca[2])
{
printf("sizeof ca : %ld \n", sizeof(ca));
printf("&ca : %p \n", &ca);
printf("&(ca[0]) : %p \n", &(ca[0]));
printf("&(ca[1]) : %p \n", &(ca[1]));
printf("\n");
}
void func_in_ptr(char *pa)
{
printf("sizeof pa : %ld \n", sizeof(pa));
printf("&pa : %p \n", &pa);
printf("&(pa[0]) : %p \n", &(pa[0]));
printf("&(pa[1]) : %p \n", &(pa[1]));
printf("\n");
}
int main(void)
{
printf("sizeof ga : %ld \n", sizeof(ga));
printf("&ga : %p \n", &ga);
printf("ga : %p \n", ga);
printf("&(ga[0]) : %p \n", &(ga[0]));
printf("&(ga[1]) : %p \n", &(ga[1]));
printf("\n");
func_in_vec(ga);
func_in_ptr(ga);
return 0;
}
結果如下(有可能在不同平台會有不同結果,這邊是在x64平台的結果,不過概念是一樣的)
sizeof ga : 2 -------------(1)
&ga : 0x601048 ------------(2)
ga : 0x601048 -------------(3)
&(ga[0]) : 0x601048 -------(4)
&(ga[1]) : 0x601049 -------(5)
sizeof ca : 8 -------------(6)
&ca : 0x7fffd7b1c718 ------(7)
&(ca[0]) : 0x601048 -------(8)
&(ca[1]) : 0x601049 -------(9)
sizeof pa : 8 -------------(10)
&pa : 0x7fffd7b1c718 ------(11)
&(pa[0]) : 0x601048 -------(12)
&(pa[1]) : 0x601049 -------(13)
由(1)可看出ga的型別為2byte的陣列,代表不直接相等指標
(2)(3)可看出&ga = ga,代表雖然compiler會將陣列轉成指標,但會忽略’*’和’&’,
(6)(8)可看出若經由funtion傳入參數後皆轉為指標,因此取sizeof為8(64位元平台)
指標及二維陣列(矩陣)
在現實生活上我們有二維陣列的概念,用來作運算或儲存各種資料,但是在記憶體裡是如何儲存這些資料?記憶體只是一大塊連續的位址空間,如何實現二維陣列的概念?
事實上在記憶體裡面二維陣列就是陣列的陣列,而三維陣列則是陣列的陣列的陣列,依此類推
舉個例子來說,一個[3][2]的陣列代表的是有一個大小3的陣列裡面有大小2的陣列,共6個,參考下圖即可清楚看出,裡面的數字代表的就是程式取值時需給的二維陣列位址,橘色代表的是第一層陣列,藍色代表第二層陣列(陣列的陣列)
試著想一下以下這段程式碼會印出什麼
int main(void)
{
int a[3][2] = {{10, 20}, {30, 40}, {50, 60}};
int *b = a[0];
printf("a[0][0] = %d\n", a[0][0]);
printf("a[0][1] = %d\n", a[0][1]);
printf("a[1][1] = %d\n", a[1][1]);
printf("a[2][1] = %d\n", a[2][1]);
printf("*(b + 0) = %d\n", *(b + 0));
printf("*(b + 1) = %d\n", *(b + 1));
printf("*(b + 3) = %d\n", *(b + 3));
printf("*(b + 5) = %d\n", *(b + 5));
return 0;
}
結果如下
a[0][0] = 10 ---(1)
a[0][1] = 20 ---(2)
a[1][1] = 40 ---(3)
a[2][1] = 60 ---(4)
*(b + 0) = 10 ---(5)
*(b + 1) = 20 ---(6)
*(b + 3) = 40 ---(7)
*(b + 5) = 60 ---(8)
從下圖可看出此段程式將b指向第一層陣列a[0]的位址,搭配下圖和以上的結果可清楚看出(1) = (5)、(2) = (6)、(3) = (7)、(4) = (8),也可看出記憶體擺放的位址和二維陣列的關係
指標及三維陣列
了解指標與陣列關係之後,我們可以來看更為抽象的三維以上的陣列,事實上如果能轉換成記憶體實際擺放的位址來看,也就不會覺得抽象了,假如有一個a[2][3][4]的三維陣列,那在記憶體擺放的情形如下,橘色代表第一維陣列,藍色代表第二維陣列,綠色代表第三維陣列,
因此此陣列為一個大小為2的陣列裡面有大小為3的陣列裡面有大小為4的陣列,共24個
試著想一下以下這段程式碼會印出什麼
int main(void)
{
int a[2][3][4] = {0};
int *b = a[0][0];
int i, j, k;
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
for (k = 0; k < 4; k++)
a[i][j][k] = i + j + k;
printf("a[0][0][0] = %d\n", a[0][0][0]);
printf("a[0][0][2] = %d\n", a[0][0][2]);
printf("a[0][1][1] = %d\n", a[0][1][1]);
printf("a[0][2][3] = %d\n", a[0][2][3]);
printf("a[1][0][0] = %d\n", a[1][0][0]);
printf("a[1][2][2] = %d\n", a[1][2][2]);
printf("*(b + 0) = %d\n", *(b + 0));
printf("*(b + 2) = %d\n", *(b + 2));
printf("*(b + 5) = %d\n", *(b + 5));
printf("*(b + 11) = %d\n", *(b + 11));
printf("*(b + 12) = %d\n", *(b + 12));
printf("*(b + 22) = %d\n", *(b + 22));
return 0;
}
結果如下
a[0][0][0] = 0
a[0][0][2] = 2
a[0][1][1] = 2
a[0][2][3] = 5
a[1][0][0] = 1
a[1][2][2] = 5
*(b + 0) = 0
*(b + 2) = 2
*(b + 5) = 2
*(b + 11) = 5
*(b + 12) = 1
*(b + 22) = 5
從下圖可看出三維陣列和記憶體的關係,將指標b指到三維陣列的頭,四維以上的陣列和二維三維陣列的原理一樣,可以此類推
接下來我們可以看一下不同type的指標和陣列的關係,首先先看int的指標,試著想一下以下這段程式碼會印出什麼
int main(void)
{
int a[2][3][4] = {0};
int *b = a[0][0];
int *c = a[0][2];
int *d = a[1][1];
int i, j, k;
int cnt = 0;
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
for (k = 0; k < 4; k++) {
a[i][j][k] = cnt;
cnt++;
}
printf("b = %p, b + 1 = %p\n", b, b + 1);
printf("*(b + 0) = %d, *(b + 1) = %d\n", *(b + 0), *(b + 1));
printf("a[0][0][0] = %d, a[0][0][1] = %d\n\n", a[0][0][0], a[0][0][1]);
printf("*(c + 0) = %d, *(c + 6) = %d\n", *(c + 0), *(c + 6));
printf("a[0][2][0] = %d, a[1][0][2] = %d\n\n", a[0][2][0], a[1][0][2]);
printf("*(d + 0) = %d, *(d + 7) = %d\n", *(d + 0), *(d + 7));
printf("a[1][1][0] = %d, a[1][2][3] = %d\n\n", a[1][1][0], a[1][2][3]);
return 0;
}
結果如下
b = 0x7fffedac8880, b + 1 = 0x7fffedac8884
*(b + 0) = 0, *(b + 1) = 1
a[0][0][0] = 0, a[0][0][1] = 1
*(c + 0) = 8, *(c + 6) = 14
a[0][2][0] = 8, a[1][0][2] = 14
*(d + 0) = 16, *(d + 7) = 23
a[1][1][0] = 16, a[1][2][3] = 23
b因為指到的是int type,所以b和b+1的差距為4byte
b, c, d皆為指到int的指標,因此可指到此三維陣列的第三維陣列,大小為4(藍色框住的綠色部份)
b指到a[0][0]的大小為4的陣列,因此*(b + 0) = a[0][0][0],*(b + 1)會加一個int size(4byte)之後取值所以*(b + 1) = a[0][0][1]
c指到a[0][2]的大小為4的陣列,因此*(c + 0) = a[0][2][0],*(c + 6)會加六個int size(4byte)之後取值所以*(c + 6) = a[1][0][2]
d指到a[1][1]的大小為4的陣列,因此*(d + 0) = a[1][1][0],*(d + 7)會加七個int size(4byte)之後取值所以*(d + 7) = a[1][2][3]
再來看int[]的指標,試著想一下以下這段程式碼會印出什麼
int main(void)
{
int a[2][3][4] = {0};
int (*b)[4] = a[0];
int (*c)[4] = a[1];
int i, j, k;
int cnt = 0;
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
for (k = 0; k < 4; k++) {
a[i][j][k] = cnt;
cnt++;
}
printf("b = %p, b + 1 = %p\n", b, b + 1);
printf("(*(b + 0))[0] = %d, (*(b + 1))[0] = %d\n", (*(b + 0))[0], (*(b + 1))[0]);
printf("a[0][0][0] = %d, a[0][0][1] = %d\n\n", a[0][0][0], a[0][1][0]);
printf("(*(b + 2))[2] = %d, (*(b + 4))[3] = %d\n", (*(b + 2))[2], (*(b + 4))[3]);
printf("a[0][2][2] = %d, a[1][1][3] = %d\n\n", a[0][2][2], a[1][1][3]);
printf("(*(c + 1))[1] = %d, (*(c + 2))[2] = %d\n", (*(c + 1))[1], (*(c + 2))[2]);
printf("a[1][1][1] = %d, a[1][2][2] = %d\n\n", a[1][1][1], a[1][2][2]);
return 0;
}
結果如下
b = 0x7fff2c1a96c0, b + 1 = 0x7fff2c1a96d0
(*(b + 0))[0] = 0, (*(b + 1))[0] = 4
a[0][0][0] = 0, a[0][1][0] = 4
(*(b + 2))[2] = 10, (*(b + 4))[3] = 19
a[0][2][2] = 10, a[1][1][3] = 19
(*(c + 1))[1] = 17, (*(c + 2))[2] = 22
a[1][1][1] = 17, a[1][2][2] = 22
b因為是指到int[4]的指標因此b和b+1的差距為16byte(4byte(int) * 4(size))
b, c, d皆為指到int[4]的指標,因此可指到此三維陣列的第二維陣列,大小為3(橘色框住的藍色部份)
b指到a[0]的大小為3的陣列,因此(*(b + 0))[0] = a[0][0][0],*(b + 1)會加四個int size(16byte)之後取值所以(*(b + 1))[0] = a[0][1][0],*(b + 2)會加八個int size(32byte)之後取值所以(*(b + 2))[2] = a[0][2][2],*(b + 4)會加十六個int size(64byte)之後取值所以(*(b + 4))[3] = a[1][1][3]
c指到a[1]的大小為4的陣列,因此(*(c + 1))[1] = a[1][1][1],*(c + 2)會加八個int size(32byte)之後取值所以(*(c + 2))[2] = a[1][2][2]
再來看int[][]的指標,試著想一下以下這段程式碼會印出什麼
int main(void)
{
int a[2][3][4] = {0};
int (*b)[3][4] = a;
int i, j, k;
int cnt = 0;
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
for (k = 0; k < 4; k++) {
a[i][j][k] = cnt;
cnt++;
}
printf("b = %p, b + 1 = %p\n", b, b + 1);
printf("(*(b + 0))[0][0] = %d, (*(b + 0))[0][2] = %d\n", (*(b + 0))[0][0], (*(b + 0))[0][2]);
printf("a[0][0][0] = %d, a[0][0][2] = %d\n\n", a[0][0][0], a[0][0][2]);
printf("(*(b + 0))[2][1] = %d, (*(b + 0))[2][3] = %d\n", (*(b + 0))[2][1], (*(b + 0))[2][3]);
printf("a[0][2][1] = %d, a[0][2][3] = %d\n\n", a[0][2][1], a[0][2][3]);
printf("(*(b + 1))[0][1] = %d, (*(b + 1))[2][3] = %d\n", (*(b + 1))[0][1], (*(b + 1))[2][3]);
printf("a[1][0][1] = %d, a[1][2][3] = %d\n\n", a[1][0][1], a[1][2][3]);
return 0;
}
結果如下
b = 0x7fff1604c440, b + 1 = 0x7fff1604c470
(*(b + 0))[0][0] = 0, (*(b + 0))[0][2] = 2
a[0][0][0] = 0, a[0][0][2] = 2
(*(b + 0))[2][1] = 9, (*(b + 0))[2][3] = 11
a[0][2][1] = 9, a[0][2][3] = 11
(*(b + 1))[0][1] = 13, (*(b + 1))[2][3] = 23
a[1][0][1] = 13, a[1][2][3] = 23
b因為是指到int[3][4]的指標因此b和b+1的差距為48byte(4byte(int) (3 4)(size))
b, c, d皆為指到int[3][4]的指標,因此可指到此三維陣列的第一維陣列,大小為3 * 4(橘色部份)
b指到a的大小為3 4的陣列,因此(\(b + 0))[0][0] = a[0][0][0],(*(b + 0))[0][2] = a[0][0][2],(*(b + 0))[2][1] = a[0][2][1],(*(b + 0))[2][3] = a[0][2][3],*(b + 1)會加十二個int size(48byte)之後取值所以(*(b + 1))[0][1] = a[1][0][1],(*(b + 1))[2][3] = a[1][2][3],