數組名代表了存放該數組的那塊內存,它是這塊內存的首地址。這就說明了數組名是一個地址,而且,還是一個不可修改的常量,完整地說,就是一個地址常量。數組名跟枚舉常量一樣,都屬于符號常量。數組名這個符號,就代表了那塊內存的首地址。注意了!不是數組名這個符號的值是那塊內存的首地址,而是數組名這個符號本身就代表了首地址這個地址值,它就是這個地址。這就是數組名屬于符號常量的意義所在。由于數組名是一種符號常量,它是一個右值,而指針,作為變量,卻是一個左值,一個右值永遠都不是左值,那么,數組名永遠都不會是指針! 對于這段話我是這么理解的:數組名在經過編譯之后將變成一個數值,這個數值就是該數組的首地址。由于數組名是一個地址,那么把它賦給一個指針變量也就不足為奇了。 例如有定義 char a[14]; char * p; char * q; void foo(char * pt) { }; 考慮以下幾種賦值: p=a;//合法,將一個地址賦給一個指針變量; q=p;//合法,將一個指針變量的值賦給另一個指針變量; a=p;//非法,a是數組名即地址,不是一個變量,不可被賦值(也就是上文中說的"數組名是右值") 再看這幾種調用: foo(a);//將一個地址作為參數傳入函數,函數中用一個指針變量接收這個地址值 foo(p);//將一個指針變量的值傳入函數(也是一個地址),函數中用一個指針變量接收這個地址 可以看出許多時候數組名和指針可以等同地看待,而c也把它們看作是兼容的類型對待,這就是為什么我那個錯誤的聲明不被編譯器在語法檢查的時候“喀嚓”的原因。 關于extern的作用,許多地方都有說明,例如可以在c++里進行c格式函數的聲明,可以聲明一個變量或函數是外部變量或外部函數;我們這里要討論的是外部變量的聲明。被extern修飾的全局變量不被分配空間,而是在連接的時候到別的文件中通過查找索引定位該全局變量的地址。 有了這些基礎后,我們現在正式開始研究extern 數組和extern 指針的問題: 首先在一個.c文件中有如下定義: char a[]={1,2,3,4}; 分析:這是一個數組變量的定義,編譯器將為這個數組分配4字節的空間,并且建立一個索引,把這個數組名、數組類型和它被分配的空間首地址對應起來。它被編譯之后生成一個中間文件 然后我們在另一個.c文件中分別以不同的形式進行聲明: (1) extern char a[]; 分析:這是一個外部變量的聲明,它聲明了一個名為a的字符數組,編譯器看到這個聲明就知道不必為這個變量分配空間,這個.c文件中所有對數組a的引用都化為一個不包含類型的標號,具體地址的定位留給連接器完成。編譯完成之后也得到一個中間文件,連接器遍歷這個文件,發現有未經定位的標號,于是它搜索其他中間文件,試圖尋找到一個匹配的空間地址,在此例中無疑連接器將成功地尋找到這個地址并將此中間文件中所有的這個標號替換為連接器所尋找到的地址,最終生成的可執行文件中,所有曾經的標號都應當已經被替換為地址。這是一個正常工作過程,連接出來的可執行文件至少在對于該數組的引用部分將工作得很好。 (2) extern char * a; 分析:這是一個外部變量的聲明,它聲明了一個名為a的字符指針,編譯器看到這個聲明就知道不必為這個指針變量分配空間,這個.c文件中所有對指針a的引用都化為一個不包含類型的標號,具體地址的定位留給連接器完成。編譯完成之后仍然得到一個中間文件,連接器遍歷這個文件,發現有未經定位的標號,于是它搜索其他中間文件,試圖尋找到一個匹配的空間地址,經過一番搜索,找到了一個分配過空間的名為a的地方(也就是我們先定義的那個字符數組),連接器并不知道它們的類型,僅僅是發現它們的名字一樣,就認為應該把extern聲明的標號連接到數組a的首地址上,因此連接器把指針a對應的標號替換為數組a的首地址。這里問題就出現了:由于在這個文件中聲明的a是一個指針變量而不是數組,連接器的行為實際上是把指針a自身的地址定位到了另一個.c文件中定義的數組首地址之上,而不是我們所希望的把數組的首地址賦予指針a(這很容易理解:指針變量也需要占用空間,如果說把數組的首地址賦給了指針a,那么指針a本身在哪里存放呢?)。這就是癥結所在了。所以此例中指針a的內容實際上變成了數組a首地址開始的4字節表示的地址(如果在16位機上,就是2字節)。本例中指針a的初值將會是0x0a090807(little endian),顯然不是我們的期望值,所以運行會出錯也就理所應當了。 ? 幾點細節:我們發現,使用extern修飾的變量在連接的時候只找尋同名的標號,不檢查類型,例如如果我們定義的甚至不是一個變量而是一個全局的函數,比如去掉定義 char a[]={....}; 代之以 void a(){}; 連接器居然也會連接通過。 |
實例如下:
比如在a.c文件中有這樣一段代碼
int g_i[] = {1, 2, 3, 4};
extern void testdotp();
void main(void)
{
int i = 0;
int num = 0;
num = sizeof(g_i) / sizeof(int);
for (i = 0; i < num; i++)
{
printf("g_i[%d] = %d ", i, g_i[i]);
}
printf("\n");
testdotp();
}
而在b.c中的代碼如下:
extern int *g_i;
void testdotp()
{
printf("*(&g_i + 2) = %d\n", *(&g_i + 2));
printf("&g_i = %d\n", &g_i);
printf("&g_i + 1= %d\n", &g_i + 1);
printf("g_i = %d\n", g_i);
printf("g_i + 1 = %d\n", g_i + 1);
}
運行結果為
g_i[0] = 1 g_i[1] = 2 g_i[2] = 3 g_i[3] = 4
*(&g_i + 2) = 3
&g_i = 4344368
&g_i + 1= 4344372
g_i = 1
g_i + 1 = 5
分析如下:
因為b.c文件中g_i變量的地址是a.c文件中g_i數組的首地址,故g_i的值為g_i[0]的值,&g_i的值為g_i地址的首地址。
而*(&g_i + 2)的值:&g_i的值為g_i數組的首地址,(&g_i + 2)就為數組g_i第3個元素的地址,*(&g_i + 2)就為第2個元素的值,即3。
&g_i + 1:由于&g_i的值為g_i數組首地址,由于在32位機上運行,故&g_i + 1在&g_i基礎上加上4個字節
g_i + 1:由于g_i是一個指針變量,g_i變量內存放的是地址,又因為g_i的值為1,而g_i + 1就為1 + 4的單元的內存空間(32位機上),故g_i + 1為5。