C/C++?extern關(guān)鍵字用法示例全面解析
前言
extern 是C/C++語言中表明全局變量或者函數(shù)作用范圍(可見性)的關(guān)鍵字,編譯器收到extern通知,則其聲明的變量或者函數(shù)可以在本模塊或者其他模塊使用。
對于函數(shù)而言,由于函數(shù)的聲明如“extern int method();”與函數(shù)定義“int method(){}”可以很清晰的區(qū)分開來,為了簡便起見,可以把extern關(guān)鍵字省略,于是有了我們常見的函數(shù)聲明方式“int method();”,然而對于變量并非如此,變量的定義格式如“int i;”,聲明格式為“extern int i;”,如果省略extern關(guān)鍵字,就會造成混亂,故不允許省略。
一般用法
在本模塊中使用:
extern int a; extern int b; int maxbb(int l,int r) { return l > r ? l : r; } int main() { cout << maxbb(a, b) << endl; } int a = 10; int b = 20;
當(dāng)前模塊的main函數(shù)在a和b之前定義,所以main函數(shù)對a和b是沒有訪問權(quán)限的,可以在main之前定義
extern int a; extern int b;
這樣就可以正常訪問到a和b了。
跨模塊中
在模塊1 _extern.cpp中定義:
extern int _a = 10; extern int _b = 20; int maxAB(int a,int b) { return a > b ? a : b; }
在模塊2 main.cpp中定義:
extern int _a; extern int _b; int maxAB(int a,int b); int main() { cout << "a:" << _a << " b:" << _b << endl; cout << maxAB(100,200) << endl; } 結(jié)果: a:10 b:20 200
看到使用extern關(guān)鍵字使用到了外部模塊_extern.cpp中的全局變量以及函數(shù)。
標(biāo)準(zhǔn)定義使用extern關(guān)鍵字的步驟為:
- 1.定義一個.h文件用來聲明需要提供外部訪問的變量或者函數(shù)。
module1.h extern int _a; extern int _b; int maxAB(int a, int b);
2.定義一個.cpp文件來初始化全局變量或者函數(shù)的實現(xiàn)
module1.cpp #include "module1.h" int _a = 100; int _b = 200; int maxAB(int x, int y) { return x > y ? x : y; }
3.在需要使用到的地方使用extern關(guān)鍵字修飾。
//main.cpp extern int _a; extern int _b; int maxAB(int a,int b); int main() { cout << "a:" << _a << " b:" << _b << endl; cout << maxAB(100,200) << endl; } 運行結(jié)果: a:100 b:200 200
如果我們把module1.h中如下定義:
extern int _a = 100; extern int _b = 200;
這樣肯定會報錯的,因為extern int _a是變量聲明,而extern int _a = 100則是變量聲明和定義。 因為module1.cpp中是將"module1.h" include到cpp中的,如果在.h中聲明和定義即使用extern int _a = 100方式,則會引起重復(fù)定義,而extern int _a是變量聲明,并非定義,所有不會重復(fù)。
extern 使用過程中的一些注意事項
這里引用掘友出的題:
數(shù)組通過外部聲明為指針時,數(shù)組和指針是不能互換使用的;那么請思考一下,在 A 文件中定義數(shù)組 char a[100];在 B 文件中聲明為指針:extern char *a;此時訪問 a[i],會發(fā)生什么;
先說結(jié)果,會引起 segmentation fault 報錯;
這里涉及到了數(shù)組與指針的區(qū)別
數(shù)組與指針的區(qū)別
數(shù)組變量和枚舉常量一樣都屬于符號常量。注意,不是數(shù)組變量這個符號的值是那塊內(nèi)存的首地址, 而是數(shù)組變量這個符號本身代表了首地址,它就是這個地址值。這就是數(shù)組變量屬于符號常量的意義所在。
由于數(shù)組變量是一個符號常量,所以其可以看做是右值,而指針作為變量,只能看作為左值。 右值永遠(yuǎn)不等于左值,所以將指針賦予數(shù)組常量是不合法的。
例如:char a[] 中的 a 是常量,是一個地址,char *a 中 a 是一個變量,一個可以存放地址的變量。
extern 聲明全局變量的內(nèi)部實現(xiàn)
被extern修飾的全局變量,在編譯期不會分配空間,而是在鏈接的時候通過索引去別的文件中查找索引對應(yīng)的地址。假設(shè)文件中聲明了一個:
extern char a[];
這是一個外部變量的聲明,聲明了一個外部的字符數(shù)組,編譯器看到這玩意時不會立即給a分配空間,而是等鏈接器進(jìn)行尋址,編譯器會將所有關(guān)于a的引用化為一個不包含類型的標(biāo)號,編譯完成后會得到一個目標(biāo)中間產(chǎn)物a.o,但是此時a.o中關(guān)于a還是一個無類型標(biāo)號,鏈接器連接的時候發(fā)現(xiàn)這個標(biāo)號,會去其他 中間產(chǎn)物中查找和這個標(biāo)號對應(yīng)的地址,找到之后替換這個標(biāo)號。最后鏈接為一個可執(zhí)行的文件。
extern char * a;
這也是一個外部變量的聲明,它聲明了一個字符指針。編譯以及鏈接過程和前面字符數(shù)組過程類似,只是此時鏈接器在尋找符號地址的時候,找到的是前面聲明的 extern char a[] 字符數(shù)組,這里就有問題了 :由于在這個文件中聲明的 a 是一個指針變量而不是數(shù)組,鏈接器的行為實際上是把指針 a 自身的地址定位到了另一個 .cpp 文件中定義的數(shù)組首地址上, 而不是我們所希望的把數(shù)組的首地址賦予指針 a。(這很容易理解:指針變量也需要占用空間,如果說把數(shù)組的首地址賦給了指針 a,那么指針 a 本身在哪里存放呢?) 這就是癥結(jié)所在了。所以此例中指針 a 的內(nèi)容實際上變成了數(shù)組 a 首地址開始的 4 字節(jié)表示的地址
上述加粗部分的可以理解為,鏈接器認(rèn)為 a 變量本身的內(nèi)存位置是數(shù)組的首地址,但其實 a 的位置是其他位置,其內(nèi)容才是數(shù)組首地址。
這里著重要理解的是:指針的地址以及指針的內(nèi)容的區(qū)別,指針本身也存在地址,鏈接器將數(shù)組的首地址賦予了指針本身,這樣肯定是不行的。
舉個例子,定義 char a[] = "abcd",則外部變量 extern char a[] 的地址是 0x12345678 (數(shù)組的起始地址), 而 extern char *a 是重新定義了一個指針變量 a,其地址可能是 0x87654321,因此直接使用 extern char *a 是錯誤的。
通過上述分析,我們得到的最重要的結(jié)論是:使用 extern 修飾的變量在鏈接的時候只找尋同名的標(biāo)號,不檢查類型,所以才會導(dǎo)致編譯通過,運行時出錯。
extern "C"
extern "C"的真實目的是實現(xiàn)類C和C++的混合編程。在C++源文件中的語句前面加上extern "C",表明它按照類C的編譯和連接規(guī)約來編譯和連接,而不是C++的編譯的連接規(guī)約。這樣在類C的代碼中就可以調(diào)用C++的函數(shù)or變量等。(注:我在這里所說的類C,代表的是跟C語言的編譯和連接方式一致的所有語言)
C和C++互相調(diào)用
前面我們說了extern “C”是為了實現(xiàn)C和C++混編,接下來就來講解下C與C++如何相互調(diào)用,在講解相互調(diào)用之前,我們先來了解C和C++編譯和鏈接過程的差異。
C++的編譯和鏈接
大家都知道C++是一個面向?qū)ο蟮木幊谭绞?,而面向?qū)ο笞詈诵牡奶匦跃褪侵剌d,函數(shù)重載給我們帶來了很大便利性。假設(shè)定義如下函數(shù)重載方法:
void log(int i); void log(char c); void log(float f); void log(char* c);
則在編譯后:
_log_int _log_char _log_float _log_string
編譯后的函數(shù)名通過帶上參數(shù)的類型信息,這樣連接時根據(jù)參數(shù)就可以找到正確的重載方法。
C++中給的變量編譯也是這樣一個過程,如全局變量會編譯為g_xx,類變量編譯為c_xx.連接時也是按照這種機制去查找對應(yīng)的變量的。
C的編譯和連接
C語言中并沒有重載和類這些特性,故不會像C++一樣將log(int i)編譯為log_int,而是直接編譯為log函數(shù),當(dāng)C++去調(diào)用C中的log(int i)方法時,會找不到_log_int方法,此時extern “C”的作用就體現(xiàn)出來了。
下面來看下C和C++是如何互相調(diào)用的。
C++中調(diào)用C的代碼
假設(shè)一個C的頭文件cHeader.h中聲明了一個函數(shù)_log(int i),如果C++要調(diào)用它,則必須添加上extern關(guān)鍵字。代碼如下:
//cHeader.h #ifndef C_HEADER #define C_HEADER extern void _log(int i); #endif // !C_HEADER
在對應(yīng)的cHeader.c文件中實現(xiàn)_log方法:
//cHeader.c #include "cHeader.h" #include <stdio.h> void _log(int i) { printf("cHeader %d\n", i); }
在C++中引用cHeader中的_log方法:
//main.cpp extern "C" { //void _log(int i); #include "cHeader.h" } int main() { _log(100); }
linux執(zhí)行上述文件的命令為:
- 1.首先執(zhí)行g(shù)cc -c cHeader.c,會產(chǎn)生cHeader.o;
- 2.然后執(zhí)行g(shù)++ -o C++ main.cpp cHeader.o
- 3.執(zhí)行程序輸出:Header 100
注意: 在main.cpp文件中可以不用包含函數(shù)聲明的文件,即“extern "C"{#include"cHeader.h"}”,而直接改用extern "C" void log(int i)的形式。那main.cpp是如何找到C中的log函數(shù),并調(diào)用的呢?
那是因為首先通過gcc -c cHeader.c生成一個目標(biāo)文件cHeader.o,然后我們通過執(zhí)行g(shù)++ -o C++ main.cpp cHeader.o這個命令指明了需要鏈接的目標(biāo)文件cHeader.o。 main.cpp中只需要聲明哪些函數(shù)需要以C的形式調(diào)用,然后去目標(biāo)文件中查找即可。“.o”為目標(biāo)文件。類似Windows中的obj文件。
C中調(diào)用C++的代碼
C中調(diào)用C++中的代碼和前面的有所不同,首先在cppHeader.h中聲明一個_log_i方法。
#pragma once extern "C" { void _log_i(int i); }
在對應(yīng)的cppHeader.cpp中實現(xiàn)該方法:
#include "cppHeader.h" #include <stdio.h> void _log_i(int i) { printf("cppHeader:%d\n", i); }
定義一個cMain.c文件調(diào)用_log_i方法:
extern void _log_i(int i); int main() { _log_i(120); }
注意點:
- 1.如果直接在.c文件中include “cppHeader.h”是會報錯的,因為cppHeader.h中包含了extern “C”,而將cppHeader.h包含進(jìn)來,會直接展開cppHeader.h內(nèi)容,而extern “C”在C語言中是不支持的,所以會報錯。
- 2.在.c文件中不加extern void _log_i(int i)也會報錯
linux執(zhí)行上述文件的命令為:
(1)首先執(zhí)行命令:g++ cppHeader.cpp -fpic -shared -g -o cppHeader.so 該命令是將cppHeader.cpp編譯成動態(tài)連接庫,其中編譯參數(shù)的解釋如下:
- -shared 該選項指定生成動態(tài)連接庫(讓連接器生成T類型的導(dǎo)出符號表,有時候也生成弱連接W類型的導(dǎo)出符號),不用該標(biāo)志外部程序無法連接。相當(dāng)于一個可執(zhí)行文件
- -fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關(guān)的所以動態(tài)載入時是通過代碼拷貝的方式來滿足不同進(jìn)程的需要,而不能達(dá)到真正代碼段共享的目的。
- -g:為調(diào)試
(2)然后再執(zhí)行命令:gcc cMain.c cppHeader.so -o cmain 該命令是編譯cMain.c文件,同時鏈接cppHeader.so文件,然后產(chǎn)生cmain的可執(zhí)行文件。
(3)最后執(zhí)行命令: ./cmain 來執(zhí)行該可執(zhí)行程序
結(jié)果:cppHeader:120
總結(jié)
本文主要講解了關(guān)于extern的三個知識點:
- 1.extern的基礎(chǔ)用法:本模塊以及跨模塊的使用
- 2.extern的在使用過程中的一些注意點,主要通過數(shù)組和指針的區(qū)別來講解。
- 3.extern “C”在C++中的用法以及原理:講解了關(guān)于C和C++互相調(diào)用以及內(nèi)部實現(xiàn)機制。
參考
extern “C“ 用法詳細(xì)說明 extern關(guān)鍵字用法詳解 【C/C++】extern 的一些注意事項
以上就是C/C++ extern關(guān)鍵字用法示例全面解析的詳細(xì)內(nèi)容,更多關(guān)于C/C++ extern關(guān)鍵字用法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++實現(xiàn)LeetCode(135.分糖果問題)
這篇文章主要介紹了C++實現(xiàn)LeetCode(135.分糖果問題),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C語言實現(xiàn) 數(shù)據(jù)類型占多少字節(jié)指針占多少字節(jié)
這篇文章主要介紹了 C語言 數(shù)據(jù)類型占多少字節(jié)指針占多少字節(jié)的實例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09一起來了解一下C++的結(jié)構(gòu)體?struct
這篇文章主要為大家詳細(xì)介紹了C++的結(jié)構(gòu)體struct,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02