extern "c"的用法

青山牧云人發表於2024-10-24

在我的印象裡,extern "c"一直是c++呼叫c介面用的,用法是用exetrn "c"把相應寫好的c介面包住。

即在 C++ 出現以前,很多程式碼都是 C 語言寫的,而且很底層的庫也是 C 語言寫的,為了更好的支援原來的 C 程式碼和已經寫好的 C 語言庫,需要在 C++ 中儘可能的支援 C ,而 extern “C” 就是其中的一個策略。 extern “C” 主要作用就是為了能夠正確實現 C++ 程式碼呼叫其他 C 語言程式碼。 extern “C” 會指示編譯器這部分程式碼按 C 語言的進行編譯,而不是 C++。

但是在最近的工作中同事提到c呼叫c++時同樣用到extern "c"語法,驚訝之餘把這塊內容好好看了一下,有了很多收穫:

1.到底什麼是extern "c"

理解這個問題的關鍵在於函式過載: c++中支援函式過載,c語言不支援函式過載,這造成了c++和c語言的函式名解析不同。c語言函式名就是函式名,c++的函式名是函式名+引數組合起來的。

C語言的函式名稱解析僅僅基於函式名稱;而C++的函式名稱解析基於函式名稱和引數列表。

比如,函式void func(double a)CC++中的編譯階段函式名稱會被解析成什麼呢?

C語言中,由於沒有名稱修飾,所以在編譯時函式名稱仍然是func,不會因為引數型別或數量而改變。

C++中,由於名稱修飾的存在,函式名稱在編譯階段會被編譯器轉換成一個包含函式原型資訊的唯一識別符號。通常會涉及函式返回型別、引數型別以及引數數量。以GCC(GNU Compiler Collection)為例,func(double a)會被轉換成_Z4funcd ,這裡:

  • _Z:是GCC用來表示修飾名稱的字首
  • 4:表示函式名稱func的的字元數
  • d:是double型別的編碼

因此,用c++的方式去尋找c語言的符號是無法尋找到的。extern "C"為何可以做到?

extern "C"的作用就是修改了符號表的生成方式,將c++符號的生成方式換成了c的生成方式。

c庫中生成的符號是c編譯器的符號, 因此c語言可以直接連結。而c++程式需要使用extern "C"讓編譯器使用c的符號命名方式去進行連結,這樣才能找到對應的符號。

2.extern "c"到底是用來c++呼叫c,還是c

直接說答案:都可以。

3.c++ call c和 c call c++的實戰

3.1Minimal runnable C from C++ example

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

c.h

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++ 
 * because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c

#include "c.h"

int f(void) { return 1; }

run

g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

Without extern "C" the link fails with:

main.cpp:6: undefined reference to `f()'

3.2Minimal runnable C++ from C example

main.c

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

cpp.h

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

run

gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

Without extern "C" it fails with:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

分析:著重看c呼叫c++,這裡巧妙利用了#ifdef __cplusplus,使得當用cpp編譯時,函式還是f。當沒用c++編譯時,函式就變成了f_int和f_float。這裡的f_int和f_float就是呼叫了f。

這樣就同時滿足了c和c++呼叫該函式的需求,我即可以在c++中呼叫該介面,也可以在c語言中呼叫該介面。

注意:如果編譯時直接用命令“g++ main.c cpp.cpp”則不會報錯,即使沒有用extern "C"。這是因為C++是相容C的語法的,如果在編譯的時候,c檔案就是用g++編譯的,則函式的命名規則會變得跟cpp檔案中的函式一樣,那麼肯定不會報錯了

4 進階:C語言呼叫c++中的成員函式

在C程式中呼叫C++成員函式需要遵循幾個步驟:

  1. 確保C++成員函式是可以從C訪問的,即它必須是extern "C"的,這樣它會使用C的連結方式。

  2. 使用C++的extern "C"宣告來匯出函式,以便C可以連結到它。

  3. 確保C++物件例項的存在,並且在C中正確地透過函式指標呼叫成員函式。

// myclass.h
class MyClass {
public:
    void myFunction();
    static void myStaticFunction();
};
 
extern "C" void MyClass_myFunction(MyClass* obj);
extern "C" void MyClass_myStaticFunction();
// myclass.cpp
#include "myclass.h"
 
void MyClass::myFunction() {
    // 實現
}
 
void MyClass::myStaticFunction() {
    // 實現
}
 
extern "C" void MyClass_myFunction(MyClass* obj) {
    obj->myFunction();
}
 
extern "C" void MyClass_myStaticFunction() {
    MyClass::myStaticFunction();
}
// main.c
#include "myclass.h"
 
int main() {
    // 呼叫靜態成員函式
    MyClass_myStaticFunction();
 
    // 呼叫非靜態成員函式
    MyClass obj;
    MyClass_myFunction(&obj);
 
    return 0;
}

C++類MyClass有一個非靜態成員函式myFunction()和一個靜態成員函式myStaticFunction()。透過定義額外的extern "C"函式,我們可以從C中呼叫這些成員函式。注意,對於非靜態成員函式,你需要一個MyClass例項,並透過C函式指標呼叫它的成員函式。

參考連結:

  • What is the effect of extern "C" in C++? https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c
  • extern "C"如何使用? https://zhuanlan.zhihu.com/p/709910309
  • c++中的extern ”C“ https://www.zhihu.com/tardis/bd/art/634091433?source_id=1001
  • c語言和c++的相互呼叫 https://blog.csdn.net/qq_29344757/article/details/73332501

相關文章