extern使用说明
1 基本解釋
extern可以置于變量或者函數前,以標示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。
另外,extern也可用來進行鏈接指定。
2 問題:extern 變量
在一個源文件里定義了一個數組:char a[6];
在另外一個文件里用下列語句進行了聲明:extern char *a;
請問,這樣可以嗎?
答案與分析:
1)、不可以,程序運行時會告訴你非法訪問。原因在于,指向類型T的指針并不等價于類型T的數組。extern char *a聲明的是一個指針變量而不是字符數組,因此與實際的定義不同,從而造成運行時非法訪問。應該將聲明改為extern char a[ ]。
2)、例子分析如下,如果a[] = "abcd",則外部變量a=0x61626364 (abcd的ASCII碼值),*a顯然沒有意義
顯然a指向的空間(0x61626364)沒有意義,易出現非法內存訪問。
3)、這提示我們,在使用extern時候要嚴格對應聲明時的格式,在實際編程中,這樣的錯誤屢見不鮮。
4)、extern用在變量聲明中常常有這樣一個作用,你在*.c文件中聲明了一個全局的變量,這個全局的變量如果要被引用,就放在*.h中并用extern來聲明。
4 問題:extern 函數2
當函數提供方單方面修改函數原型時,如果使用方不知情繼續沿用原來的extern申明,這樣編譯時編譯器不會報錯。但是在運行過程中,因為少了或者多了輸入參數,往往會照成系統錯誤,這種情況應該如何解決?
答案與分析:
目前業界針對這種情況的處理沒有一個很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供對外部接口的聲明,然后調用方include該頭文件,從而省去extern這一步。以避免這種錯誤。
寶劍有雙鋒,對extern的應用,不同的場合應該選擇不同的做法。
5 問題:extern “C”
在C++環境下使用C函數的時候,常常會出現編譯器無法找到obj模塊中的C函數定義,從而導致鏈接失敗的情況,應該如何解決這種情況呢?
答案與分析:
C++語言在編譯的時候為了解決函數的多態問題,會將函數名和參數聯合起來生成一個中間的函數名稱,而C語言則不會,因此會造成鏈接時找不到對應函數的情況,此時C函數就需要用extern “C”進行鏈接指定,這告訴編譯器,請保持我的名稱,不要給我生成用于鏈接的中間函數名。
下面是一個標準的寫法:
//在.h文件的頭上
#ifdef __cplusplus
#if __cplusplus
extern "C"{
#endif
#endif /* __cplusplus */
…
…
//.h文件結束的地方
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */
3 問題:extern 函數1
常常見extern放在函數的前面成為函數聲明的一部分,那么,C語言的關鍵字extern在函數的聲明中起什么作用?
答案與分析:
如果函數的聲明中帶有關鍵字extern,僅僅是暗示這個函數可能在別的源文件里定義,沒有其它作用。即下述兩個函數聲明沒有明顯的區別:
extern int f(); 和int f();
當然,這樣的用處還是有的,就是在程序中取代include “*.h”來聲明函數,在一些復雜的項目中,我比較習慣在所有的函數聲明前添加extern修飾。
-----------------------------------------------------------
extern數組與extern指針
數組名代表了存放該數組的那塊內存,它是這塊內存的首地址。這就說明了數組名是一個地址,而且,還是一個不可修改的常量,完整地說,就是一個地址常量。數組名跟枚舉常量一樣,都屬于符號常量。數組名這個符號,就代表了那塊內存的首地址。注意了!不是數組名這個符號的值是那塊內存的首地址,而是數組名這個符號本身就代表了首地址這個地址值,它就是這個地址。這就是數組名屬于符號常量的意義所在。由于數組名是一種符號常量,它是一個右值,而指針,作為變量,卻是一個左值,一個右值永遠都不是左值,那么,數組名永遠都不會是指針!
對于這段話我是這么理解的:數組名在經過編譯之后將變成一個數值,這個數值就是該數組的首地址。由于數組名是一個地址,那么把它賦給一個指針變量也就不足為奇了。
例如有定義
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)(4321),顯然不是我們的期望值,所以運行會出錯也就理所應當了。
?
幾點細節:我們發現,使用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);
printf("in main: g_i =%d", i, g_i);
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);
}
運行結果為
in main: g_i =134518852 g_i[0] = 1 g_i[1] = 2 g_i[2] = 3 g_i[3] = 4 /n
in b.c:
*(&g_i + 2) = 3
&g_i = 134518852
&g_i + 1= 134518856
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。
?
轉載于:https://www.cnblogs.com/york-hust/archive/2012/05/30/2526455.html
總結
以上是生活随笔為你收集整理的extern使用说明的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 贷款年利率5.6%是多少
- 下一篇: 【Demo 0085】导出EXE文件资源