C/C++開發(fā)中extern的一些使用注意事項
前言
前些日子,有友友問了我這樣的一道問題:
數(shù)組通過外部聲明為指針時,數(shù)組和指針是不能互換使用的;那么請思考一下,在 A 文件中定義數(shù)組 char a[100]
;在 B 文件中聲明為指針:extern char *a
;此時訪問 a[i]
,會發(fā)生什么;
先說結(jié)果,會引起 segmentation fault
報錯;
那接下來由博主來分析一番;
數(shù)組與指針的區(qū)別
在介紹 extern
之前,我們需要了解一下數(shù)組與指針有什么區(qū)別?
數(shù)組變量代表了存放該數(shù)組的那塊內(nèi)存,它是這塊內(nèi)存的首地址。這就說明了數(shù)組變量是一個地址,而且,還是一個不可修改的常量,具體來說,就是一個地址常量。
數(shù)組變量跟枚舉常量一樣,都屬于符號常量。數(shù)組變量這個符號,就代表了那塊內(nèi)存的首地址。注意,不是數(shù)組變量這個符號的值是那塊內(nèi)存的首地址,而是數(shù)組變量這個符號本身代表了首地址,它就是這個地址值。這就是數(shù)組變量屬于符號常量的意義所在。
由于數(shù)組變量是一種符號常量,它是一個右值,而指針,作為變量,卻是一個左值,一個右值永遠都不是左值,那么,數(shù)組名永遠都不會是指針!
舉個例子,char a[]
中的 a
是常量,是一個地址,char *a
中 a
是一個變量,一個可以存放地址的變量。
具體分析
了解了數(shù)組與指針的區(qū)別之后,讓我們來看看 extern
聲明全局變量的內(nèi)部實現(xiàn);
extern
是 C/C++ 語言中表明函數(shù)和全局變量作用范圍(可見性)的關(guān)鍵字,該關(guān)鍵字告訴編譯器,其聲明的函數(shù)和變量可以在本模塊或其它模塊中使用。
TIP :被 extern
修飾的全局變量不被分配空間,而是在鏈接的時候到別的文件中通過查找索引定位該全局變量的地址。
extern char a[];
這是一個外部變量的聲明,它聲明了一個名為 a
的字符數(shù)組,編譯器看到這個聲明就知道不必為這個變量分配空間,這個 .cpp 文件中所有對數(shù)組 a
的引用都化為一個不包含類型的標號,具體地址的定位留給鏈接器完成。編譯完成之后也得到一個中間文件,鏈接器遍歷這個文件,發(fā)現(xiàn)有未經(jīng)定位的標號,于是它搜索其他中間文件,試圖尋找到一個匹配的空間地址,在此例中無疑鏈接器將成功地尋找到這個地址并將此中間文件中所有的這個標號替換為鏈接器所尋找到的地址,最終生成的可執(zhí)行文件中,所有曾經(jīng)的標號都應當已經(jīng)被替換為地址。這是一個正常工作過程,鏈接出來的可執(zhí)行文件至少在對于該數(shù)組的引用部分將工作得很好。
extern char * a;
這是一個外部變量的聲明,它聲明了一個名為 a
的字符指針,中間過程與上同,經(jīng)過一番搜索,找到了一個分配過空間的名為 a
的地方(也就是我們先定義的那個字符數(shù)組),鏈接器并不知道它們的類型,僅僅是發(fā)現(xiàn)它們的名字一樣,就認為應該把 extern
聲明的標號鏈接到數(shù)組 a
的首地址上,因此鏈接器把指針 a
對應的標號替換為數(shù)組 a
的首地址。這里問題就出現(xiàn)了:由于在這個文件中聲明的 a
是一個指針變量而不是數(shù)組,鏈接器的行為實際上是把指針 a
自身的地址定位到了另一個 .c 文件中定義的數(shù)組首地址上,而不是我們所希望的把數(shù)組的首地址賦予指針 a
(這很容易理解:指針變量也需要占用空間,如果說把數(shù)組的首地址賦給了指針 a
,那么指針 a
本身在哪里存放呢?)。這就是癥結(jié)所在了。所以此例中指針 a
的內(nèi)容實際上變成了數(shù)組 a
首地址開始的 4 字節(jié)表示的地址(如果在 16 位機上,就是 2 字節(jié))。
上述加粗部分的可以理解為,鏈接器認為 a
變量本身的內(nèi)存位置是數(shù)組的首地址,但其實 a 的位置是其他位置,其內(nèi)容才是數(shù)組首地址。
舉個例子,定義 char a[] = "abcd"
,則外部變量 extern char a[]
的地址是 0x12345678 (數(shù)組的起始地址),而 extern char *a
是重新定義了一個指針變量 a
,其地址可能是 0x87654321,因此直接使用 extern char *a
是錯誤的。
通過上述分析,我們得到的最重要的結(jié)論是:使用 extern
修飾的變量在鏈接的時候只找尋同名的標號,不檢查類型,所以才會導致編譯通過,運行時出錯。
extern "C"
extern "C"
包含雙重含義,從字面上即可得到:
- 首先,被它修飾的目標是
extern
的; - 其次,被它修飾的目標是
C
的。
1、 被 extern "C"
限定的函數(shù)或變量是 extern
類型的;
extern int a;
僅僅是一個變量的聲明,其并不是在定義變量 a
,并未為 a
分配內(nèi)存空間。變量 a
在所有模塊中作為一種全局變量只能被定義一次,否則會出現(xiàn)連接錯誤。
通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數(shù)和全局變量以關(guān)鍵字 extern
聲明。例如,如果模塊 B 欲引用該模塊 A 中定義的全局變量和函數(shù)時只需包含模塊 A 的頭文件即可。這樣,模塊 B 中調(diào)用模塊 A 中的函數(shù)時,在編譯階段,模塊 B 雖然找不到該函數(shù),但是并不會報錯,它會在連接階段中從模塊 A 編譯生成的目標代碼中找到此函數(shù)。
與 extern
對應的關(guān)鍵字是 static
,被它修飾的全局變量和函數(shù)只能在本模塊中使用。因此,一個函數(shù)或變量只可能被本模塊使用時,其不可能被 extern "C"
修飾。
2、被 extern "C"
修飾的變量和函數(shù)是按照 C 語言方式編譯和連接的;
未加 extern "C"
聲明時的編譯方式
作為一種面向?qū)ο蟮恼Z言,C++ 支持函數(shù)重載,而過程式語言 C 則不支持。函數(shù)被 C++ 編譯后在符號庫中的名字與 C 語言的不同。例如,假設(shè)某個函數(shù)的原型為:
void foo( int x, int y );
該函數(shù)被 C 編譯器編譯后在符號庫中的名字為 _foo
,而 C++ 編譯器則會產(chǎn)生像 _foo_int_int
之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機制,生成的新名字稱為 “mangled name”)。
_foo_int_int
這樣的名字包含了函數(shù)名、函數(shù)參數(shù)數(shù)量及類型信息,C++ 就是靠這種機制來實現(xiàn)函數(shù)重載的。例如,在 C++ 中,函數(shù) void foo(int x, int y)
與 void foo(int x, float y)
編譯生成的符號是不相同的,后者為 _foo_int_float
。
同樣地,C++ 中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,我們以 .
來區(qū)分。而本質(zhì)上,編譯器在進行編譯時,與函數(shù)的處理相似,也為類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不同。
未加 extern "C"
聲明時的連接方式
假設(shè)在 C++ 中,模塊 A 的頭文件如下:
// 模塊A頭文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H int foo(int x, int y); #endif
在模塊 B 中引用該函數(shù):
// 模塊B實現(xiàn)文件 moduleB.cpp #include "moduleA.h" foo(2, 3);
實際上,在連接階段,連接器會從模塊 A 生成的目標文件 moduleA.obj 中尋找 _foo_int_int
這樣的符號;
加 extern "C"
聲明后的編譯和連接方式
加 extern "C"
聲明后,模塊 A 的頭文件變?yōu)椋?/p>
// 模塊A頭文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H extern "C" int foo(int x, int y); #endif
在模塊 B 的實現(xiàn)文件中仍然調(diào)用 foo(2, 3)
,其結(jié)果是:
- 模塊 A 編譯生成
foo
的目標代碼時,沒有對其名字進行特殊處理,采用了 C 語言的方式; - 連接器在為模塊 B 的目標代碼尋找
foo(2, 3)
調(diào)用時,尋找的是未經(jīng)修改的符號名_foo
;
如果在模塊 A 中函數(shù)聲明了 foo
為 extern "C"
類型,而模塊 B 中包含的是 extern int foo(int x, int y)
,則模塊 B 找不到模塊 A 中的函數(shù);反之亦然。
所以,可以用一句話概括 extern "C"
這個聲明的真實目的:實現(xiàn) C++ 與 C 及其它語言的混合編程。
以上就是C/C++開發(fā)中extern的一些使用注意事項的詳細內(nèi)容,更多關(guān)于C/C++開發(fā)extern使用事項的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++實現(xiàn)LeetCode(170.兩數(shù)之和之三 - 數(shù)據(jù)結(jié)構(gòu)設(shè)計)
這篇文章主要介紹了C++實現(xiàn)LeetCode(170.兩數(shù)之和之三 - 數(shù)據(jù)結(jié)構(gòu)設(shè)計),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08C語言中關(guān)于庫函數(shù) qsort 的模擬實現(xiàn)過程
庫函數(shù)的模擬實現(xiàn)有利于我們?nèi)ド钊肓私膺@個函數(shù)內(nèi)部是怎樣實現(xiàn)的,以及學習它的算法,使我們更加了解這個函數(shù)該怎樣去使用,接下來我將詳細的介紹qsort的應用及用法,并且用代碼模擬實現(xiàn)它們的功能2021-09-09C語言數(shù)據(jù)結(jié)構(gòu)順序表中的增刪改(尾插尾刪)教程示例詳解
這篇文章主要為大家介紹了C語言數(shù)據(jù)結(jié)構(gòu)順序表中的增刪改教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-02-02c++獲取sqlite3數(shù)據(jù)庫表中所有字段的方法小結(jié)
本文給大家分享c++獲取sqlite3數(shù)據(jù)庫表中所有字段的三種常用方法,本文針對每一種方法給大家詳細介紹,需要的的朋友通過本文一起學習吧2016-11-11