《More Effective C++》《雜項討論——34、如何在同一個程序中結合C++和C》

文章目錄

  • 1、Terms34:如何在同一個程序中結合C++和C
    • 1.1 名稱重整
    • 1.2 statics的初始化
    • 1.3 動態內存的分配
    • 1.4 數據結構的兼容性
  • 2、總結
  • 3、參考

1、Terms34:如何在同一個程序中結合C++和C

在大型項目中一般都用C++進行開發,但是不可避免會用一些C語言進行底層的調用。在確定C++和C的編譯器都能產生兼容的目標文件之后你要重點考慮四件事情:名稱重整,statics的初始化,動態內存的分配,數據結構的兼容性。

1.1 名稱重整

一、問題引出
在 C++ 出現之前,很多實用的功能都是用 C 語言開發的,很多底層的庫也是用 C 語言編寫的,如果在 C++ 代碼中可以兼容 C 語言代碼,無疑能極大地提高 C++ 程序員的開發效率。但是在一個項目中,能否既包含 C++ 程序又包含 C 程序呢?
答案是可以的,但是要小心處理,因為C++ 和 C 在程序的編譯、鏈接等方面都存在一定的差異,這些差異往往會導致程序運行失敗。
C++編譯器會為程序中的每個函數編出獨一無二的名稱;但是在C語言中沒有必要,因為C語言不支持函數重載。但是發展到現在,C++項目中幾乎都會有一些函數擁有相同的名稱,但是重載并不兼容大部分鏈接器,因此名稱重整(修飾),是對鏈接器的一個讓步。
例如你有一個函數funca(),被編譯器重整為xyzzy,你可以使用funca()的名稱,沒人在乎編譯器重整的名稱。但是如果funca處于C函數庫中,你的C++原始代碼會有一個頭文件,有如下聲明。

void funca(int x1,int x2,int x3,int x4);
//調用未經重整的函數名稱

當你使用funca(a,b,c,d)時,你的目標文件內含的是這樣的代碼:

xyzzy(a,b,c,d)
//調用重整后的函數名稱

但是如果funca()是一個C函數,那么funca()代碼在目標文件會有一個名為funca的函數,名稱并未重整。當你試圖鏈接那個目標文件,就會獲得一個錯誤信息,因為鏈接器尋找xyzzy的函數,并不存在。
解決方法就是告訴C++編譯器,不要重整某些函數名稱。如果調用一個名為funca的C函數,它的真正名稱就叫做funca,你的目標代碼內含有一份reference,指向那個名稱,而非一個重整后的名稱。

舉個例子:
比如下面是一個用 C++ 和 C 混合編程實現的實例項目:
myfun.h文件內容:

void display();

myfun.c文件內容:

#include <stdio.h>
#include "myfun.h"
void display(){printf("C++:http://c.biancheng/net/cplus/");
}

main.cpp文件內容:

#include <iostream>
#include "myfun.h"
using namespace std;
int main(){display();return 0;
}

可見主程序mian.cpp文件是用 C++ 編寫的,而 display() 函數的定義myfun.c文件是用 C 編寫的。
表面上看這個項目很完整,但調用 GCC 編譯器運行此項目(見利用GCC編譯器編譯C/C++程序),提示錯誤信息如下:

In function `main': undefined reference to `display()'

它表示編譯器無法找到 main.cpp 文件中 display() 函數的實現代碼。
導致此錯誤的原因,是 C++ 和 C 編譯程序時,對函數名的處理方式不同。
(1)通過函數重載詳解可知,C++ 之所以支持函數的重載,是因為在程序的編譯階段,C++會對函數的函數名進行“重命名”,比如:

void Swap(int a, int b) 會被重命名為_Swap_int_int;
void Swap(float x, float y) 會被重命名為_Swap_float_float。

(2)但是C 語言不支持函數重載,它不會在編譯階段對函數的名稱做較大的改動,比如:

void Swap(int a, int b) 會被重命名為_Swap;
void Swap(float x, float y)也會被重命名為_Swap。

不同的編譯器有不同的重命名方式,但根據 C++ 標準編譯后的函數名幾乎都由原有函數名和各個參數的數據類型構成,而根據 C 語言標準編譯后的函數名則僅由原函數名構成。
(3)這就意味著,使用 C 和 C++ 進行混合編程時,兩者對函數名的處理方式不同,勢必會造成編譯器在程序鏈接階段無法找到函數具體的實現,導致鏈接失敗。
(4)幸運的是,C++ 給出了相應的解決方案,即借助 extern “C”,就可以輕松解決 C++ 和 C 在處理代碼方式上的差異性。
二、extern "C"詳解
extern 是C和C++的一個關鍵字,但我們可以將 extern “C” 看做一個整體,和 extern 毫無關系。
extern “C” 既可以修飾一句 C++ 代碼,也可以修飾一段 C++ 代碼。
它的功能是讓編譯器以處理 C 語言代碼的方式,來處理它所修飾的 C++ 代碼。
仍以上面的例子進行說明。main.cpp 和 myfun.c 文件中都包含 myfun.h 頭文件,當程序進行預處理操作時,myfun.h 頭文件中的內容會被分別復制到這 2 個源文件中。對于 main.cpp 文件中包含的 display() 函數來說,編譯器會以 C++ 代碼的編譯方式來處理它;而對于 myfun.c 文件中的 display() 函數來說,編譯器會以 C 語言代碼的編譯方式來處理它。
為了避免 display() 函數以不同的編譯方式處理,我們應該使其在 main.cpp 文件中仍以 C 語言代碼的方式處理,這樣就可以解決函數名不一致的問題。因此,可以像如下這樣來修改 myfun.h:

#ifdef __cplusplusextern "C" void display();
#elsevoid display();
#endif

可以看到,當 myfun.h 被引入到 C++ 程序中時,會選擇帶有 extern “C” 修飾的 display() 函數;反之如果 myfun.h 被引入到 C 語言程序中,則會選擇不帶 extern “C” 修飾的 display() 函數。由此,無論 display() 函數位于 C++ 程序還是 C 語言程序,都保證了 display() 函數可以按照 C 語言的標準來處理。
再次運行該項目,會發現之前的問題消失了,可以正常運行:

C++:http://c.biancheng/net/cplus/

在實際開發中,對于解決 C++ 和 C 混合編程的問題,通常在頭文件中使用如下格式:

#ifdef __cplusplusextern "C" {
#endifvoid display();#ifdef __cplusplus}
#endif

由此可以看出,extern “C” 大致有 2 種用法,當僅修飾一句 C++ 代碼時,直接將其添加到該函數代碼的開頭即可;如果用于修飾一段 C++ 代碼,只需為 extern “C” 添加一對大括號{},并將要修飾的代碼囊括到括號內即可。

1.2 statics的初始化

許多代碼會在main之前和之后執行代碼。更明確說,static class對象、全局對象、namespace內對象以及文件范圍(file scope)內的對象,其constructors總是在main之前執行,這個過程稱為static initialization。通過static initialization產生出來的對象,其destructors必須在所謂的static destruction過程中被調用。那是發生在main結束之后。
經過編譯的main,看起來像這樣:

int main()
{performStaticInitialization();	//此行由編譯器加入the statements you put in main go here;performStaticDestruction();		//此行由編譯器加入
}

重點是:如果一個C++編譯器采用這種方法來構造和析構對象,那么除非程序中有main,否則這種對象既不會被構造也不會被析構。
有時候,在C成分中撰寫main似乎比較合理——如果程序主要以C完成而C++只是個支持庫的話。盡管如此,C++程序庫中內含static對象仍是極有可能的,所以如果能夠,還是盡量在C++中撰寫main的好。然而這并非意味你需要重寫你的C代碼。只要將你的C main重新命名的realMain,然后讓C++ main調用realMain:

extern "C"
int realMain(int argc,char* argv[]); //以C語言完成此函數int main(int argc,char* argv[])
{realMain(argc,argv);
}

1.3 動態內存的分配

動態分配規則很簡單:程序的C++部分使用new和delete,程序的C部分則使用malloc和free。
其次,嚴密地將new/delete與malloc/free分隔開來。
有時候說比做容易很多,考慮粗糙(但好用)的strdup函數,它雖然并非C或C++標準的一份子,卻被廣泛使用:

char* strdup(const char* ps); //返回一個ps所指字符串的副本

strdup分配的內存必須由strdup的調用者負責釋放。如果它自C函數庫,使用free;如果它來自一個C++程序庫,那么應該用delete。因此調用strdup后,你應該做的事情不只隨系統的不同而不同,也隨編譯器的不用而不同。為了降低這種頭痛的移植問題,請避免調用標準程序庫以外的函數或是大部分計算平臺上尚未穩定的函數。

1.4 數據結構的兼容性

如果你的C++和C編譯器有著兼容的輸出,兩個語言的函數便可以安全的交換對象指針、non-member函數指針或者static函數指針。很自然的,structs以及內建類型的變量也可以安全跨越C++/C邊界。
對于struct來說沒如果只是加上一些非虛函數,其內存布局應該不會改變,如果加上虛函數,或者繼承也會改變struct的布局,所以一個struct如果帶有base structs(或classes),無法和C函數交換。

2、總結

  • 確定你的C++和C編譯器產出兼容的目標文件(object files) 。
  • '將雙方都使用的函數聲明為extern “C”。
  • 如果可能,盡量在C++中撰寫main。
  • 總是以delete刪除new返回的內存;總是以free釋放malloc返回的內存。
  • 將兩個語言間的“數據結構傳遞”限制于C所能了解的形式;
  • C++ structs如果內含非虛函數,倒是不受此限制。

3、參考

3.1 《More Effective C++》
3.2 如何在同一個程序中結合C++和C

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/40345.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/40345.shtml
英文地址,請注明出處:http://en.pswp.cn/web/40345.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【寵粉贈書】UML 2.5基礎、建模與設計實踐

為了回饋粉絲們的厚愛&#xff0c;今天小智給大家送上一套系統建模學習的必備書籍——《UML 2.5基礎、建模與設計實踐》。下面我會詳細給大家介紹這本書&#xff0c;文末留有領取方式。 圖書介紹 《UML 2.5基礎、建模與設計實踐》以實戰為主旨&#xff0c;結合draw.io免費軟件…

匿名內部類

下面代碼中&#xff0c;Person24 是一個抽象類&#xff0c;這意味著它不能被直接實例化&#xff0c;只能通過繼承它的子類來實現其抽象方法。代碼片段中展示了如何使用匿名內部類來實現一個抽象類的實例。 package chapter04;public class Java24_Object_匿名內部類 {public s…

verilog行為建模(三):塊語句

目錄 1.塊語句2.延遲賦值語句 微信公眾號獲取更多FPGA相關源碼&#xff1a; 1.塊語句 塊語句用來將多個語句組織在一起&#xff0c;使得他們在語法上如同一個語句。 塊語句分為兩類&#xff1a; 順序塊&#xff1a;語句置于關鍵字begin和end之間&#xff0c;塊中的語句以順…

鴻蒙‘ohpm‘ 不是內部或外部命令,也不是可運行的程序-解決方案

&#x1f525; 博客主頁&#xff1a; 小韓本韓&#xff01; ?? 感謝大家點贊&#x1f44d;收藏?評論?? 在鴻蒙的DevEco Studio的終端下輸入 onpm -v 或者 你需要下載第三方ohpm包的時候提示‘ohpm‘ 不是內部或外部命令&#xff0c;也不是可運行的程序- 主要是因為我們…

學習測試1

計算機基礎 1、計算機范式&#xff1a;馮諾依曼機 2、存儲單元 bit、byte、KB、MB、GB3、網絡 ip、域名、ping 域名、 ipconfig測試工作的流程 ------------------------------------------------------------------------------------------- 一 編寫測試大綱 羅列測試…

C++STL函數對象的應用

STL函數對象 文章目錄 STL函數對象1.基本概念2.使用方法1. 簡單函數對象示例2. 函數對象作為算法參數3. Lambda表達式作為函數對象 2.一元謂詞和二元謂詞1.一元謂詞2.二元謂詞3.總結 3.算術仿函數1.使用示例2.Lambda表達式的替代 4.關系仿函數5.邏輯仿函數 C中的函數對象&#…

文化創新與社交媒體:探索Facebook的足跡

在過去的十多年里&#xff0c;Facebook從一個簡單的校園社交網絡發展成為全球最大的社交媒體平臺之一。它不僅改變了人們的溝通方式&#xff0c;更在許多方面推動了文化的創新和變革。本文將深入探索Facebook如何通過其平臺的演進和功能創新&#xff0c;成為文化創新的重要推動…

Ubuntu / Debian安裝FTP服務

本章教程,記錄在Ubuntu中安裝FTP服務的具體步驟。FTP默認端口:21 1、安裝 pure-ftpd sudo apt-get install pure-ftpd2、修改默認配置 # 與 centos 不同,這里需要在 /etc/pure-ftpd/conf 文件夾下執行下列命令,增加對應配置文件: # 創建 /etc/pure-ftpd/conf/PureDB 文件…

【數據結構】(6.2)堆的應用——Top-K問題(C語言)

系列文章目錄 文章目錄 系列文章目錄問題引入一、TopK 問題 是什么&#xff1f;二、TopK 問題解決思路2.1 TopK 思路2.2 隨機產生數字2.2 完整代碼2.3 驗證結果 問題引入 TopK 問題 (在一堆數據里面找到前 K 個最大 / 最小的數)。 一、TopK 問題 是什么&#xff1f; 生活中也…

2024 最新docker倉庫鏡像,6月,7月

目前下面的docker倉庫鏡像源還能使用。 vi /etc/docker/daemon.json添加如下配置{"registry-mirrors": ["https://hub.uuuadc.top", "https://docker.anyhub.us.kg", "https://dockerhub.jobcher.com", "https://dockerhub.icu&…

船舶雷達與導航系統選擇7/8防水插座的原因分析

概述 船舶雷達與導航系統在現代航海中扮演著至關重要的角色&#xff0c;它們為船舶提供準確的導航信息&#xff0c;確保航行的安全和效率。在這些系統中&#xff0c;7/8防水插座的使用尤為重要&#xff0c;因為它們能夠在惡劣的海上環境中提供穩定的電力和信號連接。接下來&am…

python的os.walk()

os.walk() 是一個非常有用的函數&#xff0c;用于在Python中遍歷文件夾樹。它返回一個生成器&#xff0c;該生成器在每次迭代時返回一個包含三個元素的元組&#xff1a;(當前文件夾的路徑&#xff0c;文件夾中的子文件夾的列表&#xff0c;文件夾中的文件的列表)。這個函數對于…

左耳聽風_007_06_如何才能擁有技術領導力

你好&#xff0c;我是陳浩老明左耳朵house.那通過上節課呢&#xff0c;相信你現在已經理解了什么才是技術領導力。 那今天呢我就來跟你繼續聊一聊怎樣才能擁有技術領導力。 首先呢你需要吃透基礎技術。 因為基礎技術啊是各種上層技術共同的技術。 吃透基礎技術是為了更好的…

Outlook發送大文件的問題是什么?怎么解決?

Outlook不僅是一款電子郵件客戶端&#xff0c;還包括日歷、任務、筆記、聯系人等功能&#xff0c;同時與Microsoft Office套件中的其他應用程序&#xff08;如Word、Excel、PowerPoint等&#xff09;集成緊密&#xff0c;方便用戶在不同應用程序之間切換&#xff0c;提高工作效…

LLM - 神經網絡的組成

1. 一個神經元的結構&#xff1a;即接受多個輸入X向量&#xff0c;在一個權重向量W和一個偏執標量b的作用下&#xff0c;經過激活函數后&#xff0c;產生一個輸出。 2. 一層神經網絡的結構&#xff1a;該層網絡里的每個神經元并行計算&#xff0c;得到各自的輸出;計算方式是輸入…

「植物大戰僵尸雜交版」保姆級攻略大全以及下載指南

植物大戰僵尸雜交版自推出以來&#xff0c;以其獨特的植物組合和策略玩法&#xff0c;迅速贏得了玩家們的喜愛。如果你正準備加入這場植物與僵尸的戰斗&#xff0c;或者已經在戰斗中尋求突破&#xff0c;那么這份保姆級的攻略大全將是你的得力助手。同時&#xff0c;我們也提供…

Mysql——數據庫約束和加簡單查詢

數據庫中的約束 在創建表格的過程中可以給某些字段追加約束條件 非空約束 NOT NULL NK create table t_user ( id int(3) not null, username varchar(10), password varchar(15) ); 唯一約束 UNIQUE UK create table t_user ( id int(3) not null, username varch…

[筆記] 高等數學在各工程門類的典型應用場景

1.應用場景 1.微積分似乎是在解算橢圓方程中引入的&#xff1f;但是這個數學工具第一次應用于現實的工程問題是什么時候&#xff1f;什么場景&#xff1f;什么問題&#xff1f; 微積分的發展確實與橢圓方程有關&#xff0c;但它最初的應用場景遠不止于此。 微積分首次被應用…

C++期末模擬

id:124 A. 一、會員積分&#xff08;期末模擬&#xff09; 題目描述 某電商網站的會員分為&#xff1a;普通、貴賓兩個級別 普通會員類Member&#xff0c;包含編號、姓名、積分三個屬性&#xff0c;編號和積分是整數&#xff0c;姓名是字符串 操作包括構造、打印、積分累加、…

【JavaWeb程序設計】Web基礎-JavaScript

目錄 一、函數與事件的使用 1. 編寫一個html頁面&#xff0c;使用Javascript完成數字的平方計算。 1.1 運行截圖 1.2 JS代碼 1.3 HTML代碼 2. 要求文本框中只能輸入字母 2.1 運行截圖 2.2 下載jquery-3.4.1并引用 2.3 JS代碼 2.4 HTML代碼 3. 在文本框分別輸入兩個…