1. 程式人生 > >C 與 C++ 介面函式相互呼叫

C 與 C++ 介面函式相互呼叫

文章目錄

一、C 或 C++ 編譯的四個步驟

(一) 預處理

在該步驟中,編譯器將源程式中以“#”開頭的語句進行處理。其中,#include 的原理是將目標檔案內容匯入本檔案。

(二) 編譯

在該步驟中,編譯器將第一步生成的各個檔案分別轉換成組合語言檔案。在該過程中,所有函式的名稱都會被轉換成一個符號作為彙編檔案中的唯一標識,對 C 語言函式一般直接用函式名稱作為其唯一標識的符號,而對於 C++ 函式在多數情況下需要在函式名稱加上各種字首或字尾才能作為其標識,比如函式 void Print(int num),如果編譯器將其視為 C 語言編譯,則該函式在彙編檔案中的符號為 Print,若視為 C++,則其符號可能為 Print_int(在 gcc 或 g++ 中函式名稱的改變還會考慮名稱空間等因素),這也是 C++ 支援函式過載的原因。

(三) 彙編

在該步驟中,編譯器將第二步生成的各個檔案分別轉換為二進位制檔案,但還不是可執行檔案。

(四) 連結

在該步驟中,編譯器會為第三步生成的每一個檔案“穿針引線”,比如 main() 函式中呼叫了 Print() 函式,還不知道 Print() 函式在哪裡,而在 Print() 函式主體所在的那個檔案中,已經標明瞭 Print() 函式的地址,所以編譯器會在 main() 函式中呼叫 Print() 函式的地方標註 Print() 函式的地址,為程式執行過程中的地址跳轉提供目標地址,而編譯器能做到這一步的前提,是 main() 函式中 Print() 函式的標識,和 Print() 函式主體所在的那個檔案中 Print() 函式的標識是一模一樣的,如果不一樣,就會觸發連結錯誤。


二、C 與 C++ 介面相互呼叫的關鍵

從上文可以得知,要呼叫一個函式有一個重要條件就是呼叫處的符號和函式主體處的符號要一模一樣,而 C 和 C++ 在編譯過程中將函式名稱改編成識別符號號的方法是不一樣的,因此相互呼叫的關鍵在於統一介面函式的識別符號號,而一般採取的方法是,用 C 函式改編的方法統一介面函式的改編方式。


三、extern “C”

extern "C" 的作用是告訴編譯器按 C 函式名稱改編的方法將修飾的函式改編成識別符號號。extern "C" 一般用在 C++ 檔案中。

extern "C" void Print(int num);
extern "C" {
    void Input(int* num);
    void Output(int num);
};

以上是 extern "C" 的兩種寫法。如此一來,以上三個函式都會按 C 的方式被改編成符號,在 gcc 或 g++ 編譯下就會被改變成 PrintInputOutput


四、C 函式呼叫 C++ 介面

(一) 呼叫非成員函式

被呼叫函式的宣告和定義如下。

/**
 * called.h
 */
#ifndef CALLED_H
#define CALLED_H

extern "C" void PrintCpp(void);

#endif
/**
 * called.cpp
 */
#include <iostream>
#include "called.h"

using namespace std;

void
PrintCpp(void) {
    cout << "I\'m cpp." << endl;
}

最終呼叫如下。

/**
 * call.c
 */
#include "called.h"

int
main(int argc, char const* argv[]) {
    PrintCpp();
    return 0;
}

(二) 呼叫類成員函式(介面函式沒有類指標)

被呼叫函式宣告和定義如下。

/**
 * called.h
 */
#ifndef CALLED_H
#define CALLED_H

class Console {
public:
    Console();
    virtual void PrintDouble(double num);
};

extern "C" void CppPrintDouble(double num);

#endif
/**
 * called.cpp
 */
#include <iostream>
#include "called.h"

using namespace std;

Console::Console() {}

void
Console::PrintDouble(double num) {
    cout << num << endl;
}

Console* console = new Console();

void
CppPrintDouble(double num) {
    console->PrintDouble(num);
}

最終呼叫如下。

/**
 * call.c
 */
#include "called.h"

int
main(int argc, char const* argv[]) {
    CppPrintDouble(3.14);
    return 0;
}

五、C++ 函式呼叫 C 介面

被呼叫函式的宣告和定義如下。

/**
 * called.h
 */
#ifndef CALLED_H
#define CALLED_H

void PrintC(void);

#endif
/**
 * called.c
 */
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif
#include "called.h"
#ifdef __cplusplus
};
#endif

void
PrintC(void) {
    printf("I\'m C.\n");
}

最終呼叫如下。

/**
 * call.cpp
 */
#ifdef __cplusplus
extern "C" {
#endif
#include "called.h"
#ifdef __cplusplus
};
#endif

int
main(int argc, char const* argv[]) {
    PrintC();
    return 0;
}

在 called.c 檔案中,#ifdef __cplusplus /*...*/ #endifextern "C" 的作用是防止 g++ 編譯器對“.c”檔案用 C++ 的方式編譯,如果用 gcc 進行編譯,則直接寫 #include "called.h" 就行。