C與C++的相互調用方法
- C與C++為什么相互調用的方式不同
- C++中調用C
- C中調用C++
- 致謝
C與C++為什么相互調用的方式不同
??C 和 C++ 之間的相互調用方式存在區別,主要是由于 C 和 C++ 語言本身的設計和特性不同。
- 函數調用和參數傳遞方式不同:C 和 C++ 在函數調用和參數傳遞方面有一些不同之處。C 使用標準的函數調用約定,而 C++ 在函數調用中可能包含額外的信息,如函數重載和默認參數。為了正確匹配函數簽名,C++ 編譯器可能會在函數名上進行名稱修飾(name mangling)。
- 函數重載和名稱修飾:C++ 支持函數重載,即可以有相同的函數名但不同的參數列表。為了在可執行文件中區分這些重載函數,C++ 編譯器會在函數名中添加一些信息,以便于重載解析。這與 C 的函數名約定不同,C 中函數名是平鋪的。
- 鏈接庫的差異:C 和 C++ 編譯器鏈接不同的標準庫。C 編譯器鏈接 C 標準庫,而 C++ 編譯器鏈接 C++ 標準庫。由于標準庫可能涉及不同的函數和數據結構,因此在鏈接階段可能會有不同的處理。
- 編譯器特性:C 和 C++ 編譯器對代碼的解析、優化、鏈接等可能會有不同的處理方式,這可能會導致在 C 和 C++ 相互調用時需要進行適當的處理。
??解決手段:為了在 C 和 C++ 之間實現相互調用,C++ 引入了 extern “C” 語法,它可以用來告訴 C++ 編譯器在函數聲明上使用 C 的調用約定,以便在鏈接階段能夠正確解析函數名。這種設計是為了在 C 和 C++ 之間實現互操作性,但由于兩者的語法和特性存在差異,因此在調用方式、編譯器行為和鏈接方式上會存在一些差異。
C++中調用C
??話不多說,直接上案例,下面是一個簡單的示例,演示了如何在 C++ 代碼中調用 C 函數:
首先分別創建三個文件:
mylib.c
、mylib.h
和main.cpp
??mylib.c
如下:
// mylib.c
#include <stdio.h>void my_c_function() {printf("This is a C function.\n");
}
??mylib.h
如下:
// mylib.h
#ifndef MYLIB_H
#define MYLIB_Hvoid my_c_function();#endif // MYLIB_H
??main.cpp
如下:
// main.cpp
#include <iostream>extern "C" {// 聲明 C 函數的原型void my_c_function();
}int main() {std::cout << "Calling a C function from C++:" << std::endl;// 調用 C 函數my_c_function();return 0;
}
??在這個示例中,我們使用了 #include "mylib.h"
來引入頭文件,并在 C++ 中調用了 my_c_function()
。這樣就能正確地在 C++ 中調用 C 函數。編譯步驟如下:
gcc -c mylib.c -o mylib.o # 編譯 C 文件為目標文件
g++ -c main.cpp -o main.o # 編譯 C++ 文件為目標文件
g++ main.o mylib.o -o app # 鏈接目標文件生成可執行文件
??編譯后的文件列表如下:
??然后運行可執行文件:./app
得到輸出結果:
??這里可以使用objdump
命令查看編譯之后的中間文件mylib.o
和main.o
的符號表:
??可以發現,my_c_function()
函數編譯出的名稱在mylib.o
和main.o
是相同。這是由于 C++ 文件中使用 extern “C” 來聲明 C 調用約定,以便 C 能夠正確解析函數名。
??我們來看看如果沒有使用extern “C” 后的編譯情況吧:
??可以發現,不使用 extern “C”, 函數 my_c_function 編譯后名稱變為了 (_Z13my_c_functionv) 。
??是由于在C++中,函數名在編譯后會根據函數的參數類型和返回類型進行名稱重整(Name Mangling),以支持函數重載等特性。這是因為C++支持函數的參數類型和個數可以不同,所以需要在編譯后為每個函數生成一個唯一的名稱。
??當你在C++中調用一個C函數時,如果不使用 extern “C” 聲明,C++ 編譯器會默認對函數名進行名稱重整。而在C語言中,函數名不會被重整。
??如果你在C++中調用了一個C函數,并且沒有使用 extern “C” 聲明,C++ 編譯器會對函數名進行名稱重整,生成一個新的名字,類似 _Z13my_c_functionv 這樣的名稱。這個過程被稱為名稱重整(Name Mangling),是為了確保函數在C++中能夠正確處理函數重載等特性。
C中調用C++
??下面還是來看一個簡單的示例,演示了如何在 C 代碼中調用 C++ 函數:
首先分別創建三個文件:
mylib.cpp
、mylib.h
和main.c
??mylib.cpp
如下:
// mylib.cpp
#include <iostream>#include "mylib.h"void my_cpp_function(int num) {std::cout << "C++ function called with number: " << num << std::endl;
}
??mylib.h
如下:
// mylib.h
#ifndef MYLIB_H
#define MYLIB_H#ifdef __cplusplus
extern "C" {
#endifvoid my_cpp_function(int num);#ifdef __cplusplus
}
#endif#endif // MYLIB_H#endif // MYLIB_H
??main.c
如下:
// c_main.c
#include <stdio.h>#include "mylib.h"int main() {printf("Calling C++ function from C\n");// Call the C++ functionmy_cpp_function(42);return 0;
}
??在這個示例中,我們使用了 #include "mylib.h"
來引入頭文件,并在 main.c
中調用了 my_cpp_function()
。這樣就能正確地在 C 中調用 C++ 函數。編譯步驟如下:
g++ -c mylib.cpp -o mylib.o # 編譯 C 文件為目標文件
gcc -o main main.c mylib.o -lstdc++ # 鏈接目標文件生成可執行文件
注釋:-lstdc++ 是用于鏈接 C++ 標準庫的編譯選項。在Linux系統中,C++ 標準庫通常被命名為 libstdc++.so,使用 -lstdc++ 編譯選項可以將這個庫鏈接到可執行文件中,以便在運行時使用C++的標準庫函數和功能。
??如果缺少 -lstdc++ 則會報錯:
??編譯后的文件列表如下:
??然后運行可執行文件:./main
得到輸出結果:
??這里解釋一下mylib.h
頭文件中的 #ifdef __cplusplus
:在main.c
文件夾中調用mylib.h
頭文件,但是 C 語言中并沒有 extern 這個關鍵字,因此,使用 #ifdef __cplusplus
來充當一個譯時候的閥門。
??總結一下:對于C調用C++的情況,沒有 extern “C” 這樣的關鍵字。您需要在C++代碼中使用 extern “C” 來確保C++函數按照C的方式進行鏈接,同時在C代碼中包含相應的頭文件并調用這些函數。
致謝
??本文的學習參考了以下文章:C與C++如何互相調用