淺談C/C++混合編程

2019-06-23     CPPLinux開發架構師

****如只想知道怎樣就能實現C/C++混合編程而不深究為什麼的話, 可以一拉到底直接看總結.****

首先, 在介紹C/C++混合編程之前, 先思考幾個問題

1. C/C++混合編程是什麼?

2. C/C++混合編程有什麼用?

3. C/C++混合編程應該怎麼實現?

下面, 簡單講講我對C/C++混合編程的理解 :


1. C/C++混合編程是什麼?

就像問題本身所說, C/C++混合編程也就是一個工程中, 在C函數中調用C++函數的方法, 在C++的函數中能夠調用C函數的方法.


2. C/C++混合編程有什麼用?

在我們日常開發中, 也許會遇到這麼一些情況, 同事A, C非常牛逼, 但是對C++一竅不通; 同事B, C++信手拈來, 但是對C卻滿頭霧水. 但是在工作中有這麼一種需求, 同事A需要用到C++的方法, 同事B需要用到C的方法, 這怎麼辦?

沒錯, 最簡單的就是, 同事A把C的代碼寫好, 然後同事B只管調用即可, 同理, 同事A只管調用同事B寫好的C++代碼, 各司其職, 提高工作效率.


3. C/C++混合編程應該怎麼實現?

那麼, 這混合編程究竟要怎麼實現呢?

在介紹之前, 我們先簡單了解下以下幾個概念

1. 函數重載

2. C++的名字改編機制

3. extern 及 extern "C"

* 函數重載(Overloading)

C++和Java中的函數重載的定義一致,

即在相同的作用域內, C++允許多個函數名稱相同, 而形參列表不同, 如下圖所示 :

函數重載

然而大家有沒有想過為什麼C++支持函數重載, 而C卻不支持函數重載呢?

這個就要涉及到C++的名字改編機制了. 請往下看~


* C++的名字改編機制

在C中,

void test(); // 該函數編譯後編譯器會對函數名稱改寫成 _test

C語言中的test()函數

C語言編譯器改名後的test()函數叫_test

ps: 不提供test()函數的實現是讓Xcode連結的時候報錯, 這樣我們才能看清楚test()函數的真面目!

void test(int a); // 該函數編譯後編譯器改寫函數名後依然是 _test

C語言中的test(int)函數

C語言編譯器改名後的test(int)函數依舊叫_test

在C++中,

void test(); // 該函數編譯後編譯器會對函數名稱改寫成 test()

C++編譯器改名後的test()函數叫test()

void test(int a); // 該函數編譯後編譯器改寫函數名後是 test(int)

C++編譯器改名後的test(int)函數叫test(int)

ps : 有的系統的編譯器會編譯成 _test_int 這種格式, 名字改編機制只是一種思路, 並沒有一種唯一的命名規範, 不同的編譯器命名規範不同, 但是思路一致! 如下圖所示 :

編譯器不同所產生的函數名可能不同

本文就舉這幾個例子, 大家可以自行嘗試重載多幾個函數, 然後動手試試看結果. 實踐才是王道!

通過上面幾個例子, 相信大家很容易就能知道為什麼C++支持重載而C不支持重載了.

因為C++有名字改編機制而C沒有!

所以在C中, 只要函數名相同, 不管你的形參列表如何南轅北轍, 編譯器均會將其編譯為同一函數名, 這樣在程序執行過程中就會造成函數調用的二義性(也就是對於相同函數名的函數, 程序並不知道應該調用哪一個函數), 這是不允許的, 所以會報錯.

然而對於C++而言, 儘管他們的函數名相同, 但是因為他們的形參列表不同, 編譯器編譯後實際上會為他們改名為不同名字的函數, 所以程序執行調用函數的時候並不會產生二義性, 因此C++允許函數重載.

這裡扯一句題外話, C++的重載被認為不是多態, 因為多態是動態運行時對方法的綁定, 而C++的函數重載最多算是編譯時的"多態". (這句話不一定正確, 請大家糾正)


* extern 及 extern "C"

extern相信大家比較熟悉, 它一般用來聲明一個函數, 全局變量的作用域. extern告訴編譯器, 其聲明的函數和變量可以供本文件或者其他文件使用. 這裡不再贅述.

extern "C" 中的C是什麼意思呢?

這裡的C不是指C語言這一門語言, 而是表示一種編譯和連結的規約. C表示符合C語言的編譯和連接規約的任何語言,如Fortran(公式翻譯)、assembler(彙編語言)等。

注意 :

extern "C" 只是指定編譯和連結的規約, 並不會影響語義, 所以在C++文件中該怎麼寫還得怎麼寫, 必須遵循C++的語法規範.

在C++源文件的語句前加上 extern "C" 的作用就是告訴編譯器, 這一段代碼按照類C的編譯和連結規約來編譯和連結(對, 也就是按照類C的函數命名規範編譯)

小技巧 : 如果有多條語句需要extern "C", 可以用{ } 括住, 例如 :

extern "C" 用法


那應該怎樣使用extern "C" 來 實現C/C++混合編程呢?

1. C中調用C++的代碼

2. C++中調用C的代碼

3. C/C++互調


* C中調用C++的代碼

廢話不多說, 上代碼.

C調用C++中定義的sum(int, int)函數

毫無疑問, 這段代碼是連結不通過的, 為什麼呢?

在C中, 編譯器會將main函數中調用的sum函數編譯為_sum, 然而遠在那邊寫在C++文件中的sum函數則被編譯為sum(int, int), 則連結的時候編譯器會報找不到_sum函數的錯誤. 如下圖

未定義的_sum函數

解決思路很明確, 只要確保函數在C和C++文件中編譯連結的規約一樣就OK了!

這時候extern "C" 就要閃亮登場了!

此時我們只需要在cpp文件中用extern "C" { } 把需要被C文件調用的函數包含即可, 如下圖

編譯成功, 輸出結果正確


* C++中調用C的代碼

C++調用C的sum(int, int)函數

同樣, 因為編譯連結函數命名規範不同導致找不到函數

此時, 我們不能像之前C調用C++的方法來解決問題, 原因是 extern "C" 並不能在C文件中使用, 況且C文件本來就遵循C的編譯和連結規約, 就算能在.c文件中使用extern "C" 也無濟於事.

此時就涉及到#include的作用了, 眾所周知, #include相當於文本拷貝, 等於把在頭文件中聲明的C函數原封不動cpy到#include "zs.h"中, 如下圖

#include

有的朋友可能就想到了, 既然函數聲明已經被copy到了C++文件中, 只需要保證C++文件中的這段函數聲明代碼按照類C的編譯, 連結規約進行編譯和連結就能保證編譯出的sum函數的名稱與C文件中的函數名稱保持一致了!

所以便有了以下方法

Done!

細心的朋友就會發現, 既然#include相當於文本拷貝, 那為何不把extern "C" 語句放到頭文件中呢?

好的, 我們試試~

果不其然, 運行成功

但是我很遺憾的告訴大家, 如果你這樣做的話, 那麼C文件就不能調用C文件的方法了!

因為C的編譯器不支持 extern "C" 語法!

這裡要引出一個宏, __cplusplus, 只要是C++文件, 編譯器就會自動定義一個這樣的宏, 我們就能利用這個宏做到C/C++的終極混編了!

C調用C函數成功

C++調用C函數成功


總結

要想寫一套C/C++均能調用的函數, 則必須按照C的方式編譯 (因為C語言不支持C++, 而C++同時支持C/C++)

要實現C/C++混合編程其實很簡單, 只需要在頭文件加幾行代碼即可, 如下圖

C/C++混合編程核心代碼

有趣的是, Objective-C的函數編譯命名規範與C語言一樣, 由此可知如果要實現C/OC/C++混合編程, 跟C/C++編程是大同小異.

獲取高級進階C++資料關注後私聊我「資料」,獲取以下免費資料

文章來源: https://twgreatdaily.com/zh-sg/svePJ2wBmyVoG_1Z9TvV.html