關于 extern C的說明

在用C++的項目源碼中,經常會不可避免的會看到下面的代碼

1 #ifdef __cplusplus
2 extern "C" {
3 #endif
4  
5     /*...*/
6  
7 #ifdef __cplusplus
8 }
9 #endif

它到底有什么用呢,你知道嗎?而且這樣的問題經常會出現在面試or筆試中。下面我就從以下幾個方面來介紹它:

———— 目錄 ————

1、#ifdef _cplusplus/#endif _cplusplus及發散
2、extern "C"
  2.1、extern關鍵字
  2.2、"C"
  2.3、小結extern "C"
3、C和C++互相調用
  3.1、C++的編譯和連接
  3.2、C的編譯和連接
  3.3、C++中調用C的代碼
  3.4、C中調用C++的代碼
4、C和C++混合調用特別之處函數指針

?—————————

1、#ifdef _cplusplus/#endif _cplusplus及發散

在介紹extern "C"之前,我們來看下#ifdef _cplusplus/#endif _cplusplus的作用。很明顯#ifdef/#endif、#ifndef/#endif用于條件編譯,#ifdef _cplusplus/#endif _cplusplus——表示如果定義了宏_cplusplus,就執行#ifdef/#endif之間的語句,否則就不執行。

在這里為什么需要#ifdef _cplusplus/#endif _cplusplus呢?因為C語言中不支持extern "C"聲明,如果你明白extern "C"的作用就知道在C中也沒有必要這樣做,這就是條件編譯的作用!在.c文件中包含了extern "C"時會出現編譯時錯誤。

既然說到了條件編譯,我就介紹它的一個重要應用——避免重復包含頭文件。還記得騰訊筆試就考過這個題目,給出類似下面的代碼(下面是我最近在研究的一個開源web服務器——Mongoose的頭文件mongoose.h中的一段代碼):

 1 #ifndef   MONGOOSE_HEADER_INCLUDED
 2 #define  MONGOOSE_HEADER_INCLUDED
 3  
 4 #ifdef __cplusplus
 5 extern "C" {
 6 #endif /* __cplusplus */
 7  
 8     /*.................................
 9      * do something here
10      *.................................
11      */
12  
13 #ifdef __cplusplus
14 }
15 #endif  /* __cplusplus */
16  
17 #endif  /* MONGOOSE_HEADER_INCLUDED */

然后叫你說明上面宏#ifndef/#endif的作用?為了解釋一個問題,我們先來看兩個事實:

  • 這個頭文件mongoose.h可能在項目中被多個源文件包含(#include "mongoose.h"),而對于一個大型項目來說,這些冗余可能導致錯誤,因為一個頭文件包含類定義或inline函數,在一個源文件中mongoose.h可能會被#include兩次(如,a.h頭文件包含了mongoose.h,而在b.c文件中#include a.h和mongoose.h)——這就會出錯(在同一個源文件中一個結構體、類等被定義了兩次)。
  • 從邏輯觀點和減少編譯時間上,都要求去除這些冗余。然而讓程序員去分析和去掉這些冗余,不僅枯燥且不太實際,最重要的是有時候又需要這種冗余來保證各個模塊的獨立

為了解決這個問題,上面代碼中的

1 #ifndef MONGOOSE_HEADER_INCLUDED 
2 #define    MONGOOSE_HEADER_INCLUDED 
3          /*……………………………*/ 
4 #endif /* MONGOOSE_HEADER_INCLUDED */    

就起作用了。如果定義了MONGOOSE_HEADER_INCLUDED,#ifndef/#endif之間的內容就被忽略掉。因此,編譯時第一次看到mongoose.h頭文件,它的內容會被讀取且給定MONGOOSE_HEADER_INCLUDED一個值。之后再次看到mongoose.h頭文件時,MONGOOSE_HEADER_INCLUDED就已經定義了,mongoose.h的內容就不會再次被讀取了。

2、extern "C"

首先從字面上分析extern "C",它由兩部分組成——extern關鍵字、"C"。下面我就從這兩個方面來解讀extern "C"的含義。

2.1、extern關鍵字

在一個項目中必須保證函數、變量、枚舉等在所有的源文件中保持一致,除非你指定定義為局部的。首先來一個例子:

1 //file1.c:
2     int x=1;
3     int f(){do something here}
4 
5 //file2.c:
6     extern int x;
7     int f();
8     void g(){x=f();}

在file2.c中g()使用的x和f()是定義在file1.c中的。extern關鍵字表明file2.c中x,僅僅是一個變量的聲明,其并不是在定義變量x,并未為x分配內存空間。變量x在所有模塊中作為一種全局變量只能被定義一次,否則會出現連接錯誤。但是可以聲明多次,且聲明必須保證類型一致,如:

 1 //file1.c:
 2     int x=1;
 3     int b=1;
 4     extern c;
 5 
 6 //file2.c:
 7     int x;// x equals to default of int type 0
 8     int f();
 9     extern double b;
10     extern int c;

在這段代碼中存在著這樣的三個錯誤:

  1. x被定義了兩次
  2. b兩次被聲明為不同的類型
  3. c被聲明了兩次,但卻沒有定義

回到extern關鍵字,extern是C/C++語言中表明函數全局變量作用范圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量可以在本模塊或其它模塊中使用。通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變量和函數時只需包含模塊A的頭文件即可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,但是并不會報錯;它會在連接階段中從模塊A編譯生成的目標代碼中找到此函數。

與extern對應的關鍵字是 static,被它修飾的全局變量和函數只能在本模塊中使用。因此,一個函數或變量只可能被本模塊使用時,其不可能被extern “C”修飾。

2.2、"C"

典型的,一個C++程序包含其它語言編寫的部分代碼。類似的,C++編寫的代碼片段可能被使用在其它語言編寫的代碼中。不同語言編寫的代碼互相調用是困難的,甚至是同一種編寫的代碼但不同的編譯器編譯的代碼。例如,不同語言和同種語言的不同實現可能會在注冊變量保持參數和參數在棧上的布局,這個方面不一樣。

為了使它們遵守統一規則,可以使用extern指定一個編譯和連接規約。例如,聲明C和C++標準庫函數strcyp(),并指定它應該根據C的編譯和連接規約來鏈接:

1 extern "C" char* strcpy(char*,const char*);

注意它與下面的聲明的不同之處:

extern char* strcpy(char*,const char*);

下面的這個聲明僅表示在連接的時候調用strcpy()。

extern "C"指令非常有用,因為C和C++的近親關系。注意:extern "C"指令中的C,表示的一種編譯和連接規約,而不是一種語言。C表示符合C語言的編譯和連接規約的任何語言,如Fortran、assembler等。

還有要說明的是,extern "C"指令僅指定編譯和連接規約,但不影響語義。例如在函數聲明中,指定了extern "C",仍然要遵守C++的類型檢測、參數轉換規則。

再看下面的一個例子,為了聲明一個變量而不是定義一個變量,你必須在聲明時指定extern關鍵字,但是當你又加上了"C",它不會改變語義,但是會改變它的編譯和連接方式。

如果你有很多語言要加上extern "C",你可以將它們放到extern "C"{ }中。

2.3、小結extern "C"

通過上面兩節的分析,我們知道extern "C"的真實目的是實現類C和C++的混合編程。在C++源文件中的語句前面加上extern "C",表明它按照類C的編譯和連接規約來編譯和連接,而不是C++的編譯的連接規約。這樣在類C的代碼中就可以調用C++的函數or變量等。(注:我在這里所說的類C,代表的是跟C語言的編譯和連接方式一致的所有語言)

3、C和C++互相調用

我們既然知道extern "C"是實現的類C和C++的混合編程。下面我們就分別介紹如何在C++中調用C的代碼、C中調用C++的代碼。首先要明白C和C++互相調用,你得知道它們之間的編譯和連接差異,及如何利用extern "C"來實現相互調用。

3.1、C++的編譯和連接

C++是一個面向對象語言(雖不是純粹的面向對象語言),它支持函數的重載,重載這個特性給我們帶來了很大的便利。為了支持函數重載的這個特性,C++編譯器實際上將下面這些重載函數:

1 void print(int i);
2 void print(char c);
3 void print(float f);
4 void print(char* s);

編譯為:

1  _print_int
2  _print_char
3  _print_float
4 _pirnt_string

這樣的函數名,來唯一標識每個函數。注:不同的編譯器實現可能不一樣,但是都是利用這種機制。所以當連接是調用print(3)時,它會去查找_print_int(3)這樣的函數。下面說個題外話,正是因為這點,重載被認為不是多態,多態是運行時動態綁定(“一種接口多種實現”),如果硬要認為重載是多態,它頂多是編譯時“多態”。

C++中的變量,編譯也類似,如全局變量可能編譯g_xx,類變量編譯為c_xx等。連接是也是按照這種機制去查找相應的變量。

3.2、C的編譯和連接

C語言中并沒有重載和類這些特性,故并不像C++那樣print(int i),會被編譯為_print_int,而是直接編譯為_print等。因此如果直接在C++中調用C的函數會失敗,因為連接是調用C中的print(3)時,它會去找_print_int(3)。因此extern "C"的作用就體現出來了。

3.3、C++中調用C的代碼

假設一個C的頭文件cHeader.h中包含一個函數print(int i),為了在C++中能夠調用它,必須要加上extern關鍵字(原因在extern關鍵字那節已經介紹)。cHeader.h文件的代碼如下:

1 #ifndef C_HEADER
2 #define C_HEADER 
3 
4     extern void print(int i);
5 
6 #endif C_HEADER

相對應的實現文件為cHeader.c的代碼為:

1 #include <stdio.h>
2 #include "cHeader.h"
3 void print(int i)
4 {
5     printf("cHeader %d\n",i);
6 }

現在C++的代碼文件main.cpp(當然你也可以起名為其他的xxx.cpp文件名,只是想表達的是cpp文件去調用c文件里面的函數.) 中引用C中的print(int i)函數:

 1 #include "stdafx.h"
 2 #include<iostream>
 3 //#include<iomanip>
 4 //#include <stdio.h>
 5 //#include <malloc.h>
 6 //using namespace std;
 7 
 8 extern "C"{
 9     #include "cHeader.h"
10 }
11 
12 int main()
13 {
14     print(3);
15     system("pause");
16     return 0;
17 }

我的工程建好后是如下情況:

運行的時候會報錯:

解決方法如下:

?

然后程序就可以正常編譯運行了,程序運行結果如下:

3.4、C中調用C++的代碼

現在換成在C中調用C++的代碼,這與在C++中調用C的代碼有所不同。我們新建一個win32控制臺工程,名字為c_diao_cpp,

在cppHeader.h頭文件中定義了下面的代碼:

1 ifndef CPP_HEADER
2 #define CPP_HEADER
3 
4 extern "C" void print(int i);
5  
6 #endif CPP_HEADER

相應的實現文件cppHeader.cpp文件中代碼如下:

1 #include "cppHeader.h"
2 #include <iostream>
3 using namespace std;
4 
5 void print(int i)
6 {
7     cout<<"cppHeader "<<i<<endl;
8 }

在c_diao_cpp.c 代碼文件中調用print函數:

1 #include "stdafx.h"
2 extern void print(int i);
3 
4 int _tmain(int argc, _TCHAR* argv[])
5 {
6     print(3);
7     return 0;
8 }

工程目錄結構如下:

運行結果如下:

注意在C的代碼文件中直接#include "cppHeader.h"頭文件,編譯出錯。而且如果不加extern int print(int i)編譯也會出錯。

4、C和C++混合調用特別之處函數指針

當我們C和C++混合編程時,有時候會用一種語言定義函數指針,而在應用中將函數指針指向另一中語言定義的函數。如果C和C++共享同一中編譯和連接、函數調用機制,這樣做是可以的。然而,這樣的通用機制,通常不然假定它存在,因此我們必須小心地確保函數以期望的方式調用。

而且當指定一個函數指針的編譯和連接方式時,函數的所有類型,包括函數名、函數引入的變量也按照指定的方式編譯和連接。如下例:

typedef int (*FT) (const void* ,const void*);//style of C++extern "C"{typedef int (*CFT) (const void*,const void*);//style of Cvoid qsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
}void isort(void* p,size_t n,size_t sz,FT cmp);//style of C++
void xsort(void* p,size_t n,size_t sz,CFT cmp);//style of C//style of C
extern "C" void ysort(void* p,size_t n,size_t sz,FT cmp);int compare(const void*,const void*);//style of C++
extern "C" ccomp(const void*,const void*);//style of Cvoid f(char* v,int sz)
{//error,as qsort is style of C//but compare is style of C++qsort(v,sz,1,&compare);qsort(v,sz,1,&ccomp);//ok
     isort(v,sz,1,&compare);//ok//error,as isort is style of C++//but ccomp is style of Cisort(v,sz,1,&ccopm);
}

注意:typedef int (*FT) (const void* ,const void*),表示定義了一個函數指針的別名FT,這種函數指針指向的函數有這樣的特征:返回值為int型、有兩個參數,參數類型可以為任意類型的指針(因為為void*)。

最典型的函數指針的別名的例子是,信號處理函數signal,它的定義如下:

1 typedef void (*HANDLER)(int);
2 HANDLER signal(int ,HANDLER);

上面的代碼定義了信函處理函數signal,它的返回值類型為HANDLER,有兩個參數分別為int、HANDLER。 這樣避免了要這樣定義signal函數:

1 void (*signal (int ,void(*)(int) ))(int)

比較之后可以明顯的體會到typedef的好處。

這篇文章主要參考了:http://www.cnblogs.com/skynet/archive/2010/07/10/1774964.html 這篇博客

?

轉載于:https://www.cnblogs.com/www-caiyin-com/p/6757916.html

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

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

相關文章

Nginx 面試 40 問

Nginx是一款輕量級的Web服務器、反向代理服務器&#xff0c;由于它的內存占用少&#xff0c;啟動極快&#xff0c;高并發能力強&#xff0c;在互聯網項目中廣泛應用。 那么關于 Nginx 的核心技術點有哪些呢&#xff1f; 什么是Nginx&#xff1f; Nginx是一個 輕量級/高性能的…

用Cocos2dx開發棋牌游戲的觀點解析

眾所周知&#xff0c;目前棋牌游戲特別的火。很多游戲公司都想在這一塊賺錢&#xff0c;可是卻不知用什么軟件比較好的去開發棋牌游戲&#xff0c;對此&#xff0c;我列出了兩款比較靠譜的軟件去開發棋牌游戲&#xff0c;希望對大家有幫助&#xff01; 第一款軟件是cocos2dx,它…

JavaWEB中讀取配置信息

第一種方法是使用java.io和java.util包&#xff0c;缺點是路徑的概念要清晰&#xff0c;例子&#xff1a; Properties prop new Properties();InputStream in getClass().getResourceAsStream("/common.properties");try {prop.load(in);pool new JedisPool(config…

我把《系統設計》系列整理成了 PDF

大家好&#xff0c;我是等天黑。相信很多朋友應該注意到了&#xff0c;我最近發了很多系統設計的文章。是的&#xff0c;到目前為止&#xff0c;已經發了有 7 篇文章。這些內容主要翻譯自 Alex Xu 的 《System Design Interview》&#xff0c;有卷一和卷二兩本。System Design …

高性能IO模型淺析

服務器端編程經常需要構造高性能的IO模型&#xff0c;常見的IO模型有四種&#xff1a; &#xff08;1&#xff09;同步阻塞IO&#xff08;Blocking IO&#xff09;&#xff1a;即傳統的IO模型。 &#xff08;2&#xff09;同步非阻塞IO&#xff08;Non-blocking IO&#xff09;…

Java線程通信的幾種方式

一、問題 有兩個線程&#xff0c;A 線程向一個集合里面依次添加元素“abc”字符串&#xff0c;一共添加十次&#xff0c;當添加到第五次的時候&#xff0c;希望 B 線程能夠收到 A 線程的通知&#xff0c;然后 B 線程執行相關的業務操作。線程間通信的模型有兩種&#xff1a;共享…

PHP個人博客項目------切切歆語博客

2019獨角獸企業重金招聘Python工程師標準>>> phpmysqlapache, ThinkPHP3.2框架開發 我的個人博客項目 適合新手練習 源碼地址下載&#xff1a;https://github.com/DickyQie/php-myblog 轉載于:https://my.oschina.net/zhangqie/blog/1785867

收發郵件之 MAILKIT

背景利用代碼發送郵件在工作中還是比較常見的&#xff0c;相信大家都用過SmtpClient來處理發送郵件的操作&#xff0c;不過這個類以及被標記已過時&#xff0c;所以介紹一個微軟推薦的庫MailKit來處理。MailKit開源地址&#xff1a;https://github.com/jstedfast/MailKit需要郵…

IOS_SearchBar搜索欄及關鍵字高亮

搜索框的效果演示: 這個就是所謂的搜索框了,那么接下來我們看看如何使用代碼來實現這個功能. 我所使用的數據是英雄聯盟的英雄名單,是一個JSON數據的txt文件, JSON數據的處理代碼如下所示: ?123456//獲取文件的路徑pathNSString *path [[NSBundle mainBundle] pathForResourc…

Java設計模式之(工廠模式)--簡單工廠模式--工廠方法模式--抽象工廠模式

工廠模式&#xff1a; 工廠模式可以分為三類&#xff1a; 1&#xff09;簡單工廠模式&#xff08;Simple Factory&#xff09; 2&#xff09;工廠方法模式&#xff08;Factory Method&#xff09; 3&#xff09;抽象工廠模式&#xff08;Abstract Factory&#xff09; 簡單工…

今天很多 CTO 都是被干掉的,因為他沒有成就業務

作者&#xff5c;喬新亮 編輯&#xff5c;鄧艷琴 我可以絲毫不開玩笑地說&#xff0c;今天&#xff0c;很多傳統企業里的研發都只是“工人”&#xff0c;哪怕是 CTO&#xff0c;充其量也只是“高級工人”&#xff0c;如果不轉換思維去成就業務&#xff0c;就只能停留在工人級…

中航工業集團金網絡(北京)電子商務有限公司副總經理劉正珩:航空“智”造的供應鏈支撐平臺...

編者按 “十三五”時期是我國貿易發展的重要戰略機遇期&#xff0c;物流產業發展迅速&#xff0c;智慧供應鏈已經成為推動流通大國向流通強國過程中的重要行動。6月2日&#xff0c;由上海市國有資產監督管理委員會、上海市郵政管理局、上海市商務委員會指導&#xff0c;上海市國…

創建、檢查和反編譯世界上(幾乎)最短的 C# 程序

創建、檢查和反編譯世界上&#xff08;幾乎&#xff09;最短的 C# 程序原文來自https://www.stevejgordon.co.uk/creating-inspecting-decompiling-the-worlds-smallest-csharp-program在這篇文章中&#xff0c;我認為創建世界上&#xff08;幾乎&#xff09;最短的 C# 程序然后…

Linux下畫原理圖和PCB

Linux下畫原理圖和PCBWindows下大名鼎鼎的Allegro和經典的Protel 99SE都是不支持Linux操作系統的。做Linux驅動開發免不了要看一下原理圖和PCB。一般的做法有三種&#xff1a; 1.主機使用Windows系統&#xff0c;將Linux裝在VMWARE之類的虛擬機中這樣能夠使用Windows下的軟件看…

配置中心 App Configuration (二):Feature Flag 功能開關特性

寫在前面Web服務開發過程中我們經常有這樣的需求&#xff1a;某些功能我必須我修改了配置才啟用&#xff0c;比如新用戶注冊送券等&#xff1b;某個功能需到特定的時間才啟用&#xff0c;過后就失效&#xff0c;比如春節活動等&#xff1b;某些功能&#xff0c;我想先對10%的用…

oracle臨時表空間

--查看臨時表空間SELECT * FROM v$tablespace;SELECT * FROM dba_tablespaces;--查看所有臨時表空間文件SELECT * FROM dba_data_files;--查看臨時臨時表空間文件SELECT * FROM dba_temp_files;--查看臨時表空間組SELECT * FROM dba_tablespace_groups; --查找默認臨時表空間SE…

ES 2022 正式發布!有哪些新特性?

2022 年 6 月 22 日&#xff0c;第 123 屆 Ecma 大會批準了 ECMAScript 2022 語言規范[1]&#xff0c;這意味著它現在正式成為標準。 1 ECMAScript 2022編輯 本次發布的編輯有&#xff1a; Shu-yu Guo[2] Michael Ficarra[3] Kevin Gibbons[4] 2 ECMAScript 2022有什么新內…

聯想(Lenovo)小新310經典版進bios方法

1&#xff0c;找到novo按鈕。 2&#xff0c;在關機的狀態下桶一下小孔&#xff0c;不用任何操作&#xff0c;電腦進入bios選擇界面。轉載于:https://www.cnblogs.com/senior-engineer/p/6761457.html

C#中的匿名類型

這節來講一下C#中的匿名類型。匿名類在C#中&#xff0c;我們可以不去顯示的聲明一個類&#xff0c;而是通過匿名類去臨時聲明一個類結構去幫助我們去完成一些功能。聲明一個匿名類&#xff0c;我們可以像下面這樣做&#xff1a;var Anonymousnew {name"charles",year…

MySQL之MHA高可用集群

目錄 一、MHA概述 1.1.MHA 是什么 1.2.MHA 的組成 1.3.MHA 的特點 二、MHA搭建準備 2.1.實驗思路 三、MHA搭建 3.1配置主從復制 3.2.安裝 MHA 軟件 3.3.故障模擬 3.4.故障修復 四、總結 一、MHA概述 1.1.MHA 是什么 1.MHA&#xff08;MasterHigh Availability&…