c/c++編程小知識/19.extern和C

這個流程我一直有疑問,其實是我對之前的編譯過程理解的不好。

1. extern

這個關鍵字的作用就是聲明該變量或函數來自於其它函數,讓函數的編譯過程能夠成功執行。如果這個函數或變量從來都沒出現過,加上extern也能讓程序編譯通過,但是鏈接的時候會報錯,例如undefined reference to錯誤。

1.1 變量

在定義變量的時候,這個extern居然可以被省略(定義時,默認均省略);在聲明變量的時候,這個extern必須添加在變量前,所以有時會讓你搞不清楚到底是聲明還是定義。或者說,變量前有extern不一定就是聲明,而變量前無extern就只能是定義。注:定義要爲變量分配內存空間;而聲明不需要爲變量分配內存空間。

extern int a; // 聲明一個全局變量 a
int a; // 定義一個全局變量 a
extern int a =0 ; // 定義一個全局變量 a 並給初值。
int a =0;    // 定義一個全局變量 a, 並給初值,

第四個等於第三個,都是定義一個可以被外部使用的全局變量,並給初值。

總之,我在寫的時候,定義是肯定不加extern的,只有聲明的時候加,我看很多代碼也都是這個亞子。

1.2 函數

函數,對於函數也一樣,也是定義和聲明,定義的時候用extern,說明這個函數是可以被外部引用的,聲明的時候用extern說明這是一個聲明。 但由於函數的定義和聲明是有區別的,定義函數要有函數體,聲明函數沒有函數體(還有以分號結尾),所以函數定義和聲明時都可以將extern省略掉,反正其他文件也是知道這個函數是在其他地方定義的,所以不加extern也行。兩者如此不同,所以省略了extern也不會有問題。

2. extern C

2.1 問題的產生

這個是在項目中遇到的,大體的僞代碼是醬嬸的,就叫TAO.cpp吧,大愛岡本多緒。

#include "a.h"


#ifdef __cplusplus
extern "C" {
#endif 
    
extern void x(void);    

void y(void)
{
    x();
}
    
#ifdef __cplusplus
}
#endif

其中x()來自於.c,而這個y()也要被送到.c中調用,但是y()是在.cpp中定義的。結果這個函數就不停的報錯,基本都是鏈接錯誤,例如這個就會報undefined reference to,如果把第8行的放到了第3行,也會報conflicts with new declaration with ‘C’ linkage,不加extern還是會報undefined reference to。我特麼徹底懵逼了,曾經認爲是我引用的a.h有問題,因爲x這個函數是沒有.h聲明,所有的使用都是通過在源文件中加extern。我也嘗試過創建TAO.h,把extern void x(void);放在那裏,現在一想就是徒勞。

後來因爲那個conflicts的報錯,我嘗試着新建了一個TAO.c,同樣的代碼引用x(),但是成功了,這足以說明是C++的問題,後來冒死在公司玩了手機,查的谷歌,在cplusplus上看到,可能是a.h的問題,我就把第一行的放到了第7行,結果一下子就過了。

2.2 extern C的祕密

這篇文章很好《再談談只針對C++編譯器/鏈接器的extern “C”------C與C++的相互調用

這涉及到兩個步驟,C++調用C和C調用C++,這也就是爲啥上面的錯誤我之前沒發生過。

2.2.1 C++ 調用C,也就是我上面問題產生的原因
// 注意: 該代碼是C++程序, 請放在.cpp文件中, 這樣確保是C++編譯器
 
#include <stdio.h>
 
extern void fun(); // 暫時騙過C++編譯器
 
int main()
{
	fun();
 
	return 0;
}

這個編譯肯定沒問題,但是鏈接指定會掛。

因爲C++中有重載的關係,所有的函數都不是原名,在調用中也是,函數的名稱會發生改變,在聲明函數的時候,尤其是extern,將必然找不到函數,因此就可以用extern告訴編譯器,在找函數的時候就用fun()這個名字找,別用別的。所以最終的結果代碼改爲


#include <stdio.h>
 
extern "C" void fun(); // 暫時騙過編譯器, 並對鏈接器說, 你要按照C規範鏈接, 去找_fun, 而不是"?fun@@YAXXZ"
 
int main()
{
	fun();
 
	return 0;
}

extern "C"的作用之一是:在C++調用C的時候, 告訴C++編譯器, 編譯的時候, 不要因爲看不到fun而傷心;鏈接的時候,不要用默認的C++規範查找, 而要採用C規範去查找。

2.2.2 C調用C++

其實原理是一樣的,也是防止重載,因此在定義函數的時候,就直接定義在extern C中

// 注意: 該代碼是C++程序, 請放在.cpp文件中, 這樣確保是C++編譯器
 
#include <stdio.h>
 
extern "C" void fun() // 按C編譯器的方式去編譯,要生成_fun, 而不是"?fun@@YAXXZ"
{
	printf("ok\n");
}
// 注意: 該代碼是C程序, 請放在.c文件中, 這樣確保是C編譯器
 
#include <stdio.h>
 
extern void fun(); // C編譯器不嚴格,本可以去掉本句,但爲了移植性, 最好不要去掉
 
int main()
{
	fun(); // C編譯器會去找_fun
 
	return 0;
}

當然,也可以採用我上面的那種用大括號把整個c函數全部包裹住。

extern "C"的作用之二是:在C調用C++的時候, 告訴C++編譯器, 編譯的時候, 不要按照C++默認的規範去編譯, 而應該按照C規範去編譯。

3. extern和頭文件

3.1 編譯過程

那涉及到多個文件的編譯過程呢?

我原來一直認爲用頭文件的方式是唯一的,然而不是,只不過用頭文件是最好的方式而已。以下的說法我在網上看到的,但是我自己親測不可用,可能是我中間哪個環節有問題,或者說他的實驗用的不好。

https://blog.csdn.net/qq_16542775/article/details/81353841

頭文件的話,源文件在調用的時候是不用加extern的,因爲在頭文件中會自己加入。如果沒有頭文件也可以,但是需要在被調用的地方加入extern

例如test.c中有個函數func(),test.h中聲明extern func(),main.c中如果需要調用func(),可以調用test.h,這樣main.c中就可以直接用。

再例如(這個我失敗了,但是網上說的成功了):test.c中定義func(),沒有test.h,main.c中加上extern func(),直接在main中使用。

3.2 問答

(a)用#include可以包含其他頭文件中變量、函數的聲明,爲什麼還要extern關鍵字?

(b)如果我想引用一個全局變量或函數a,我只要直接在源文件中包含#include (xxx.h包含了a的聲明)不就可以了麼,爲什麼還要用extern呢??

答案:如果一個文件(假設文件名A)要大量引用另一個文件(假設文件名B)中定義的變量或函數,則使用頭文件效率更高,程序結構也更規範。其他文件(例如文件名C、D等)要引用文件名B中定義的變量或函數,則只需用#include包含文件B對應的頭文件(當然,這個頭文件只有對變量或函數的聲明,絕不能有定義)即可。

那是一個被遺忘的年代,那時,編譯器只認識.c(或.cpp)文件,而不知道.h是何物的年代。

那時的人們寫了很多的.c(或.cpp)文件,漸漸地,人們發現在很多.c(或.cpp)文件中的聲明變量或函數原型是相同的,但他們卻不得不一個字一個字地重複地將這些內容敲入每個.c(或.cpp)文件。但更爲恐怖的是,當其中一個聲明有變更時,就需要檢查所有的.c(或.cpp)文件,並修改其中的聲明,啊~,簡直是世界末日降臨!

終於,有人(或許是一些人)再不能忍受這樣的折磨,他(們)將重複的部分提取出來,放在一個新文件裏,然後在需要的.c(或.cpp)文件中敲入#include XXXX這樣的語句。這樣即使某個聲明發生了變更,也再不需要到處尋找與修改了—世界還是那麼美好!

因爲這個新文件,經常被放在.c(或.cpp)文件的頭部,所以就給它起名叫做"頭文件",擴展名是.h。

從此,編譯器(其實是其中預處理器)就知道世上除了.c(或.cpp)文件,還有個.h的文件,以及一個叫做#include命令。

以上內容,有參考或複製於

https://www.runoob.com/w3cnote/extern-head-h-different.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章