日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C/C++?extern關鍵字用法示例全面解析_C 語言

作者:小余的自習室 ? 更新時間: 2023-02-10 編程語言

前言

extern 是C/C++語言中表明全局變量或者函數作用范圍(可見性)的關鍵字,編譯器收到extern通知,則其聲明的變量或者函數可以在本模塊或者其他模塊使用。

對于函數而言,由于函數的聲明如“extern int method();”與函數定義“int method(){}”可以很清晰的區分開來,為了簡便起見,可以把extern關鍵字省略,于是有了我們常見的函數聲明方式“int method();”,然而對于變量并非如此,變量的定義格式如“int i;”,聲明格式為“extern int i;”,如果省略extern關鍵字,就會造成混亂,故不允許省略。

一般用法

在本模塊中使用:

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;

當前模塊的main函數在a和b之前定義,所以main函數對a和b是沒有訪問權限的,可以在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;
}

結果:
a:10 b:20
200

看到使用extern關鍵字使用到了外部模塊_extern.cpp中的全局變量以及函數。

標準定義使用extern關鍵字的步驟為:

  • 1.定義一個.h文件用來聲明需要提供外部訪問的變量或者函數。
module1.h
extern int _a;
extern int _b;
int maxAB(int a, int b);

2.定義一個.cpp文件來初始化全局變量或者函數的實現

module1.cpp

#include "module1.h"

int _a = 100;
int _b = 200;

int maxAB(int x, int y) {
    return x > y ? x : y;
}

3.在需要使用到的地方使用extern關鍵字修飾。

//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;
}

運行結果:
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方式,則會引起重復定義,而extern int _a是變量聲明,并非定義,所有不會重復。

extern 使用過程中的一些注意事項

這里引用掘友出的題:

數組通過外部聲明為指針時,數組和指針是不能互換使用的;那么請思考一下,在 A 文件中定義數組 char a[100];在 B 文件中聲明為指針:extern char *a;此時訪問 a[i],會發生什么;

先說結果,會引起 segmentation fault 報錯;

這里涉及到了數組與指針的區別

數組與指針的區別

數組變量和枚舉常量一樣都屬于符號常量。注意,不是數組變量這個符號的值是那塊內存的首地址, 而是數組變量這個符號本身代表了首地址,它就是這個地址值。這就是數組變量屬于符號常量的意義所在。

由于數組變量是一個符號常量,所以其可以看做是右值,而指針作為變量,只能看作為左值。 右值永遠不等于左值,所以將指針賦予數組常量是不合法的。

例如:char a[] 中的 a 是常量,是一個地址,char *a 中 a 是一個變量,一個可以存放地址的變量。

extern 聲明全局變量的內部實現

被extern修飾的全局變量,在編譯期不會分配空間,而是在鏈接的時候通過索引去別的文件中查找索引對應的地址。假設文件中聲明了一個:

extern char a[];

這是一個外部變量的聲明,聲明了一個外部的字符數組,編譯器看到這玩意時不會立即給a分配空間,而是等鏈接器進行尋址,編譯器會將所有關于a的引用化為一個不包含類型的標號,編譯完成后會得到一個目標中間產物a.o,但是此時a.o中關于a還是一個無類型標號,鏈接器連接的時候發現這個標號,會去其他 中間產物中查找和這個標號對應的地址,找到之后替換這個標號。最后鏈接為一個可執行的文件。

extern char * a;

這也是一個外部變量的聲明,它聲明了一個字符指針。編譯以及鏈接過程和前面字符數組過程類似,只是此時鏈接器在尋找符號地址的時候,找到的是前面聲明的 extern char a[] 字符數組,這里就有問題了 :由于在這個文件中聲明的 a 是一個指針變量而不是數組,鏈接器的行為實際上是把指針 a 自身的地址定位到了另一個 .cpp 文件中定義的數組首地址上, 而不是我們所希望的把數組的首地址賦予指針 a。(這很容易理解:指針變量也需要占用空間,如果說把數組的首地址賦給了指針 a,那么指針 a 本身在哪里存放呢?) 這就是癥結所在了。所以此例中指針 a 的內容實際上變成了數組 a 首地址開始的 4 字節表示的地址

上述加粗部分的可以理解為,鏈接器認為 a 變量本身的內存位置是數組的首地址,但其實 a 的位置是其他位置,其內容才是數組首地址。

這里著重要理解的是:指針的地址以及指針的內容的區別,指針本身也存在地址,鏈接器將數組的首地址賦予了指針本身,這樣肯定是不行的。

舉個例子,定義 char a[] = "abcd",則外部變量 extern char a[] 的地址是 0x12345678 (數組的起始地址), 而 extern char *a 是重新定義了一個指針變量 a,其地址可能是 0x87654321,因此直接使用 extern char *a 是錯誤的。

通過上述分析,我們得到的最重要的結論是:使用 extern 修飾的變量在鏈接的時候只找尋同名的標號,不檢查類型,所以才會導致編譯通過,運行時出錯。

extern "C"

extern "C"的真實目的是實現類C和C++的混合編程。在C++源文件中的語句前面加上extern "C",表明它按照類C的編譯和連接規約來編譯和連接,而不是C++的編譯的連接規約。這樣在類C的代碼中就可以調用C++的函數or變量等。(注:我在這里所說的類C,代表的是跟C語言的編譯和連接方式一致的所有語言)

C和C++互相調用

前面我們說了extern “C”是為了實現C和C++混編,接下來就來講解下C與C++如何相互調用,在講解相互調用之前,我們先來了解C和C++編譯和鏈接過程的差異。

C++的編譯和鏈接

大家都知道C++是一個面向對象的編程方式,而面向對象最核心的特性就是重載,函數重載給我們帶來了很大便利性。假設定義如下函數重載方法:

void log(int i);
void log(char c);
void log(float f);
void log(char* c);

則在編譯后:

_log_int
_log_char
_log_float
_log_string

編譯后的函數名通過帶上參數的類型信息,這樣連接時根據參數就可以找到正確的重載方法。

C++中給的變量編譯也是這樣一個過程,如全局變量會編譯為g_xx,類變量編譯為c_xx.連接時也是按照這種機制去查找對應的變量的。

C的編譯和連接

C語言中并沒有重載和類這些特性,故不會像C++一樣將log(int i)編譯為log_int,而是直接編譯為log函數,當C++去調用C中的log(int i)方法時,會找不到_log_int方法,此時extern “C”的作用就體現出來了。

下面來看下C和C++是如何互相調用的。

C++中調用C的代碼

假設一個C的頭文件cHeader.h中聲明了一個函數_log(int i),如果C++要調用它,則必須添加上extern關鍵字。代碼如下:

//cHeader.h
#ifndef C_HEADER
#define C_HEADER
extern void _log(int i);
#endif // !C_HEADER

在對應的cHeader.c文件中實現_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執行上述文件的命令為:

  • 1.首先執行gcc -c cHeader.c,會產生cHeader.o;
  • 2.然后執行g++ -o C++ main.cpp cHeader.o
  • 3.執行程序輸出:Header 100

注意: 在main.cpp文件中可以不用包含函數聲明的文件,即“extern "C"{#include"cHeader.h"}”,而直接改用extern "C" void log(int i)的形式。那main.cpp是如何找到C中的log函數,并調用的呢?

那是因為首先通過gcc -c cHeader.c生成一個目標文件cHeader.o,然后我們通過執行g++ -o C++ main.cpp cHeader.o這個命令指明了需要鏈接的目標文件cHeader.o。 main.cpp中只需要聲明哪些函數需要以C的形式調用,然后去目標文件中查找即可?!?o”為目標文件。類似Windows中的obj文件。

C中調用C++的代碼

C中調用C++中的代碼和前面的有所不同,首先在cppHeader.h中聲明一個_log_i方法。

#pragma once
extern "C" {
    void _log_i(int i);
}

在對應的cppHeader.cpp中實現該方法:

#include "cppHeader.h"
#include &lt;stdio.h&gt;

void _log_i(int i) {
    printf("cppHeader:%d\n", i);
}

定義一個cMain.c文件調用_log_i方法:

extern void _log_i(int i);
int main() {
    _log_i(120);
}

注意點:

  • 1.如果直接在.c文件中include &ldquo;cppHeader.h”是會報錯的,因為cppHeader.h中包含了extern “C”,而將cppHeader.h包含進來,會直接展開cppHeader.h內容,而extern “C”在C語言中是不支持的,所以會報錯。
  • 2.在.c文件中不加extern void _log_i(int i)也會報錯

linux執行上述文件的命令為:

(1)首先執行命令:g++ cppHeader.cpp -fpic -shared -g -o cppHeader.so 該命令是將cppHeader.cpp編譯成動態連接庫,其中編譯參數的解釋如下:

  • -shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當于一個可執行文件
  • -fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
  • -g:為調試

(2)然后再執行命令:gcc cMain.c cppHeader.so -o cmain 該命令是編譯cMain.c文件,同時鏈接cppHeader.so文件,然后產生cmain的可執行文件。

(3)最后執行命令: ./cmain 來執行該可執行程序

結果:cppHeader:120

總結

本文主要講解了關于extern的三個知識點:

  • 1.extern的基礎用法:本模塊以及跨模塊的使用
  • 2.extern的在使用過程中的一些注意點,主要通過數組和指針的區別來講解。
  • 3.extern “C”在C++中的用法以及原理:講解了關于C和C++互相調用以及內部實現機制。

參考

extern “C“ 用法詳細說明?extern關鍵字用法詳解?【C/C++】extern 的一些注意事項

原文鏈接:https://juejin.cn/post/7184392083493355557

欄目分類
最近更新