思考前提:VS2005
可以說,從來沒有真正的用過extern,真正的思考過extern。不過現在不行了,因為要和C語言打真正的交道了。
在C工程中,這文件包含的關系處理可謂是一門高深的學問,因為它決定了編譯器的執行行進路徑。先來看一個純C小工程。
[cpp] view plaincopy
/*a.c*/
#include "b.h"
void print0(void);
int main(int argc,char** argv)
{
g_ma = 1;
printf("%d\n",g_ma);
print0();
system("pause");
return 0;
}
這個文件中首先聲明了一個函數print0(),然後在main()中調用它。同時,也調用(賦值)了全局變量g_ma。
[cpp]
/*b.h*/
#ifndef _B_H_
#define _B_H_
#include <stdio.h>
#include <stdlib.h>
int g_ma;
#endif
b.h文件中就定義了一個全局變量g_ma。
[cpp] view plaincopy
/*b.c*/
#include "b.h"
void print0(void)
{
printf("hello world\n");
}
在b.c中,定義(實現)了函數print0()。
運行結果:順利顯示期望的數值。
變種1:把a.c中的void print0(void);改為extern void print0(void);
運行結果:順利顯示期望的數值。
原因:extern關鍵字啥意思?就是聲明的意思,這與void print0(void);這句的含義不謀而合。所以有無都沒關系。
進一步:如果把這句全去掉,那麼會報錯,因為在b.h中和a.c中都沒這個print0函數的聲明,沒有聲明,編譯器怎麼知道它是啥呢。
變種2:在a.c中添加extern int g_ma;
[cpp]
#include "b.h"
void print0(void);
extern int g_ma;
int main(int argc,char** argv)
{
g_ma = 1;
printf("%d\n",g_ma);
print0();
system("pause");
return 0;
}
運行結果:順利顯示期望的數值。
原因:extern int g_ma;只是告訴編譯器有這麼一個聲明,在編譯器發現它之前,編譯器已經在#include "b.h"中發現了這個變量,所以有無都沒關系。
進一步:如果去掉extern,肯定報錯,因為int g_ma不是聲明,而是定義,這樣就和b.h中的重名了。
變種3:把b.h中的全局變量放到b.c中去,再在a.c中添加 extern int g_ma;
運行結果:這個地方就難理解了,其實不管是否有這個extern關鍵字,都會順利顯示期望的數值。
原因:先說加了extern的,編譯器發現有這個聲明後,知道,肯定在某個地方有定義。就像函數聲明似的,知道有這個類型的函數定義存在。帶到連接器執行的時候,再來最終調用g_ma。這不加extern就奇怪了,我打上二者的地址看,發現是一樣的,修改其中一個變量的名字,地址就不一樣了,猜想是編譯器發現同樣的變量名,就放在同一個靜態地址,覆蓋了。這是我理解不能的地方一
變種4:把函數的定義,變量的定義全放在b.h中,a.c中去掉全部聲明。
[cpp]
#ifndef _B_H_
#define _B_H_
#include <stdio.h>
#include <stdlib.h>
int g_ma;
void print0(void)
{
printf("hello world : %d\n",g_ma);
}
#endif
#include "b.h"
int main(int argc,char** argv)
{
g_ma = 1;
printf("%d\n",g_ma);
print0();
system("pause");
return 0;
}
運行結果:一開始想,肯定沒問題啊,a.c中的陌生變量,函數都可以在b.h中找到啊。可是報print0()函數多重定義。這是我理解不能的地方二。
進一步:如果把a.c改為a.cpp,就可以通過了,看來c和c++的編譯規則還是又很大區別的。
變種5:把a.c改為a.cpp,去掉所有聲明,然後在b.h中添加print0()的聲明。
[cpp]
#ifndef _B_H_
#define _B_H_
#include <stdio.h>
#include <stdlib.h>
int g_ma;
void print0(void);
#endif
#include "b.h"
int main(int argc,char** argv)
{
g_ma = 1;
printf("%d\n",g_ma);
print0();
system("pause");
return 0;
}
運行結果:報錯,void __cdecl print0(void)" (?print0@@YAXXZ),該符號在函數 _main 中被引用。
原因:因為c和c++對編譯後的函數名,變量名所采用的名字改編規則是不一樣的,所以c處理的b.c中的print0和c++處理的print0是不一樣的。解決方法是告訴編譯器,對於print0要采用c規則的名字改變,所以要添加extern “C”在函數名字前,不過依然報錯,因為extern “C”是c++的語法,所以要在c++文件中書寫,所以最後就是把函數聲明extern “C” 放在a.cpp文件中。
進一步:如果把全局變量g_ma也放在b.c文件中,也會報這個錯誤。
疑問:這b.h到底是怎麼被編譯的?
猜想:我們看到,這個b.h被a.cpp和b.c包含了,而一個是c++文件,另一個是c文件,雖然有防止重復包含的#ifndef語句,但鬼知道編譯器在編譯之前,會采用相應的規則去檢查語法呢。所以這個b.h會被c++和c規則檢查兩遍,所以函數聲明的擺放位置就顯得格外重要了。 www.2cto.com
結論:如果在c或c++中采用文章一開始的文件包含順序,我感覺extern關鍵字沒啥作用,有無沒關系。當c或c++混合編程的時候,這個關鍵字作用就體現了,不過它的作用是陪襯“C”
這個字符串。
作者:ma52103231