1. 程式人生 > >在C程式設計中使用到的幾個重要關鍵字之一extern

在C程式設計中使用到的幾個重要關鍵字之一extern

extern:

extern可以置於變數或者函式前,以表示變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他模組中尋找其定義。另外,extern也可用來進行連結指定。

在一個原始檔裡定義了一個陣列:char a[6];

  在另外一個檔案裡用下列語句進行了宣告:extern char *a;

  請問,這樣可以嗎?

  答案與分析:

  1)、不可以,程式執行時會告訴你非法訪問。原因在於,指向型別T的指標並不等價於型別T的陣列。extern char *a宣告的是一個指標變數而不是字元陣列,因此與實際的定義不同,從而造成執行時非法訪問。應該將宣告改為extern char a[ ]。

  2)、例子分析如下,如果a[] = "abcd",則外部變數a=0x12345678 (陣列的起始地址),而*a是重新定義了一個指標變數a的地址可能是0x87654321,直接使用*a是錯誤的.

  3)、這提示我們,在使用extern時候要嚴格對應宣告時的格式,在實際程式設計中,這樣的錯誤屢見不鮮。

  4)、extern用在變數宣告中常常有這樣一個作用,你在*.c檔案中聲明瞭一個全域性的變數,這個全域性的變數如果要被引用,就放在*.h中並用extern來宣告。

現代編譯器一般採用按檔案編譯的方式,因此在編譯時,各個檔案中定義的全域性變數是

  互相透明的,也就是說,在編譯時,全域性變數的可見域限制在檔案內部。下面舉一個簡單的例子。建立一個工程,裡面含有A.cpp和B.cpp兩個簡單的C++原始檔:

  //A.cpp

  int i;

  void main()

  {

  }

  //B.cpp

  int i;

  這兩個檔案極為簡單,在A.cpp中我們定義了一個全域性變數i,在B中我們也定義了一個全域性變數i。

  我們對A和B分別編譯,都可以正常通過編譯,但是進行連結的時候,卻出現了錯誤,錯誤提示如下:

  Linking...

  B.obj : error LNK2005: "int i" ([email protected]@3HA) already defined in A.obj

  Debug/A.exe : fatal error LNK1169: one or more multiply defined symbols found

  Error executing link.exe.

  A.exe - 2 error(s), 0 warning(s)

  這就是說,在編譯階段,各個檔案中定義的全域性變數相互是透明的,編譯A時覺察不到B中也定義了i,同樣,編譯B時覺察不到A中也定義了i。

  但是到了連結階段,要將各個檔案的內容“合為一體”,因此,如果某些檔案中定義的全域性變數名相同的話,在這個時候就會出現錯誤,也就是上面提示的重複定義的錯誤。

  因此,各個檔案中定義的全域性變數名不可相同。

  在連結階段,各個檔案的內容(實際是編譯產生的obj檔案)是被合併到一起的,因而,定義於某檔案內的全域性變數,在連結完成後,它的可見範圍被擴大到了整個程式。

  這樣一來,按道理說,一個檔案中定義的全域性變數,可以在整個程式的任何地方被使用,舉例說,如果A檔案中定義了某全域性變數,那麼B檔案中應可以使用該變數。修改我們的程式,加以驗證:

  //A.cpp

  void main()

  {

  i = 100; //試圖使用B中定義的全域性變數

  }

  //B.cpp

  int i;

  編譯結果如下:

  Compiling...

  A.cpp

  C:\Documents and Settings\wangjian\桌面\try extern\A.cpp(5) : error C2065: 'i' : undeclared identifier

  Error executing cl.exe.

  A.obj - 1 error(s), 0 warning(s)

  編譯錯誤。

  其實出現這個錯誤是意料之中的,因為:檔案中定義的全域性變數的可見性擴充套件到整個程式是在連結完成之後,而在編譯階段,他們的可見性仍侷限於各自的檔案。

  編譯器的目光不夠長遠,編譯器沒有能夠意識到,某個變數符號雖然不是本檔案定義的,但是它可能是在其它的檔案中定義的。

  雖然編譯器不夠遠見,但是我們可以給它提示,幫助它來解決上面出現的問題。這就是extern的作用了。

  extern的原理很簡單,就是告訴編譯器:“你現在編譯的檔案中,有一個識別符號雖然沒有在本檔案中定義,但是它是在別的檔案中定義的全域性變數,你要放行!”

  我們為上面的錯誤程式加上extern關鍵字:

  //A.cpp

extern int i;

  void main()

  {

  i = 100; //試圖使用B中定義的全域性變數

  }

  //B.cpp

  int i;

  順利通過編譯,連結。

extern 函式1

  常見extern放在函式的前面成為函式宣告的一部分,那麼,C語言關鍵字extern在函式的宣告中起什麼作用?

  答案與分析:

  如果函式的宣告中帶有關鍵字extern,僅僅是暗示這個函式可能在別的原始檔裡定義,沒有其它作用。即下述兩個函式宣告沒有明顯的區別:

  extern int f(); 和int f();

  當然,這樣的用處還是有的,就是在程式中取代include “*.h”來宣告函式,在一些複雜的專案中,我比較習慣在所有的函式宣告前新增extern修飾。

extern 函式2

  當函式提供方單方面修改函式原型時,如果使用方不知情繼續沿用原來的extern申明,這樣編譯時編譯器不會報錯。但是在執行過程中,因為少了或者多了輸入引數,往往會造成系統錯誤,這種情況應該如何解決?

  答案與分析:

  目前業界針對這種情況的處理沒有一個很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供對外部介面的宣告,然後呼叫方include該標頭檔案,從而省去extern這一步。以避免這種錯誤。

  寶劍有雙鋒,對extern的應用,不同的場合應該選擇不同的做法。

以上則是有關extern的一些用法,事例中有的使用C++的具體例項,但是在C中用法基本一直,如果是寫單純的C檔案,我認為到此可以結束。一下是有關一些C++與C連結的相關知識。

/*==================================================================================    *==================================================================================

  */

extern “C”

  在C++環境下使用C函式的時候,常常會出現編譯器無法找到obj模組中的C函式定義,從而導致連結失敗的情況,應該如何解決這種情況呢?

  答案與分析:

  C++語言在編譯的時候為了解決函式的多型問題,會將函式名和引數聯合起來生成一箇中間的函式名稱,而C語言則不會,因此會造成連結時找不到對應函式的情況,此時C函式就需要用extern “C”進行連結指定,這告訴編譯器,請保持我的名稱,不要給我生成用於連結的中間函式名。

  下面是一個標準的寫法:

  //在.h檔案的頭上

  #ifdef __cplusplus

  #if __cplusplus

  extern "C"{

  #endif

  #endif /* __cplusplus */

  …

  …

  //.h檔案結束的地方

  #ifdef __cplusplus

  #if __cplusplus

  }

  #endif

  #endif /* __cplusplus */

C++中extern c的深層探索

  C++語言的建立初衷是“a better C”,但是這並不意味著C++中類似C語言的全域性變數和函式所採用的編譯和連線方式與C語言完全相同。作為一種欲與C相容的語言,C++保留了一部分過程式語言的特點(被世人稱為“不徹底地面向物件”),因而它可以定義不屬於任何類的全域性變數和函式。但是,C++畢竟是一種面向物件的程式設計語言,為了支援函式的過載,C++對全域性函式的處理方式與C有明顯的不同。

  2.從標準標頭檔案說起

  某企業曾經給出如下的一道面試題:

  面試題

  為什麼標準標頭檔案都有類似以下的結構?

  #ifndef __INCvxWorksh

  #define __INCvxWorksh

  #ifdef __cplusplus

  extern "C" {

  #endif

  /*...*/

  #ifdef __cplusplus

  }

  #endif

  #endif /* __INCvxWorksh */

  分析

  顯然,標頭檔案中的編譯巨集“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止該標頭檔案被重複引用。

  那麼

  #ifdef __cplusplus

  extern "C" {

  #endif

  #ifdef __cplusplus

  }

  #endif

  的作用又是什麼呢?我們將在下文一一道來。

3.深層揭密extern "C"

  extern "C" 包含雙重含義,從字面上即可得到:首先,被它修飾的目標是“extern”的;其次,被它修飾的目標是“C”的。讓我們來詳細解讀這兩重含義。

  被extern "C"限定的函式或變數是extern型別的;

  extern是C/C++語言中表明函式和全域性變數作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其宣告的函式和變數可以在本模組或其它模組中使用。記住,下列語句:

  extern int a;

  僅僅是一個變數的宣告,其並不是在定義變數a,並未為a分配記憶體空間。變數a在所有模組中作為一種全域性變數只能被定義一次,否則會出現連線錯誤。

  通常,在模組的標頭檔案中對本模組提供給其它模組引用的函式和全域性變數以關鍵字extern宣告。例如,如果模組B欲引用該模組A中定義的全域性變數和函式時只需包含模組A的標頭檔案即可。這樣,模組B中呼叫模組A中的函式時,在編譯階段,模組B雖然找不到該函式,但是並不會報錯;它會在連線階段中從模組A編譯生成的目的碼中找到此函式。

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

  被extern "C"修飾的變數和函式是按照C語言方式編譯和連線的;

  未加extern “C”宣告時的編譯方式

  首先看看C++中對類似C的函式是怎樣編譯的。

  作為一種面向物件的語言,C++支援函式過載,而過程式語言C則不支援。函式被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函式的原型為:

  void foo( int x, int y );

  該函式被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱為“mangled name”)。

  _foo_int_int這樣的名字包含了函式名、函式引數數量及型別資訊,C++就是靠這種機制來實現函式過載的。例如,在C++中,函式void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者為_foo_int_float。

  同樣地,C++中的變數除支援區域性變數外,還支援類成員變數和全域性變數。使用者所編寫程式的類成員變數可能與全域性變數同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函式的處理相似,也為類中的變數取了一個獨一無二的名字,這個名字與使用者程式中同名的全域性變數名字不同。

  未加extern "C"宣告時的連線方式

  假設在C++中,模組A的標頭檔案如下:

  // 模組A標頭檔案 moduleA.h

  #ifndef MODULE_A_H

  #define MODULE_A_H

  int foo( int x, int y );

  #endif

  在模組B中引用該函式:

  // 模組B實現檔案 moduleB.cpp

  #include "moduleA.h"

  foo(2,3);

  實際上,在連線階段,聯結器會從模組A生成的目標檔案moduleA.obj中尋找_foo_int_int這樣的符號!

  加extern "C"聲明後的編譯和連線方式

  加extern "C"聲明後,模組A的標頭檔案變為:

  // 模組A標頭檔案 moduleA.h

  #ifndef MODULE_A_H

  #define MODULE_A_H

  extern "C" int foo( int x, int y );

  #endif

  在模組B的實現檔案中仍然呼叫foo( 2,3 ),其結果是:

  (1)模組A編譯生成foo的目的碼時,沒有對其名字進行特殊處理,採用了C語言的方式;

  (2)聯結器在為模組B的目的碼尋找foo(2,3)呼叫時,尋找的是未經修改的符號名_foo。

  如果在模組A中函式聲明瞭foo為extern "C"型別,而模組B中包含的是extern int foo( int x, int y ) ,則模組B找不到模組A中的函式;反之亦然。

  所以,可以用一句話概括extern “C”這個宣告的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎麼做的,還要問一問它為什麼要這麼做,動機是什麼,這樣我們可以更深入地理解許多問題):

  實現C++與C及其它語言的混合程式設計。

  明白了C++中extern "C"的設立動機,我們下面來具體分析extern "C"通常的使用技巧。

  4.extern "C"的慣用法

  (1)在C++中引用C語言中的函式和變數,在包含C語言標頭檔案(假設為cExample.h)時,需進行下列處理:

  extern "C"

  {

  #include "cExample.h"

  }

  而在C語言的標頭檔案中,對其外部函式只能指定為extern型別,C語言中不支援extern "C"宣告,在.c檔案中包含了extern "C"時會出現編譯語法錯誤。

  筆者編寫的C++引用C函式例子工程中包含的三個檔案的原始碼如下:

  /* c語言標頭檔案:cExample.h */

  #ifndef C_EXAMPLE_H

  #define C_EXAMPLE_H

  extern int add(int x,int y);

  #endif

  /* c語言實現檔案:cExample.c */

  #include "cExample.h"

  int add( int x, int y )

  {

  return x + y;

  }

  // c++實現檔案,呼叫add:cppFile.cpp

  extern "C"

  {

  #include "cExample.h"

  }

  int main(int argc, char* argv[])

  {

  add(2,3);

  return 0;

  }

  如果C++呼叫一個C語言編寫的.DLL時,當包括.DLL的標頭檔案或宣告介面函式時,應加extern "C" { }。

  (2)在C中引用C++語言中的函式和變數時,C++的標頭檔案需新增extern "C",但是在C語言中不能直接引用聲明瞭extern "C"的該標頭檔案,應該僅將C檔案中將C++中定義的extern "C"函式宣告為extern型別。

  筆者編寫的C引用C++函式例子工程中包含的三個檔案的原始碼如下:

  //C++標頭檔案 cppExample.h

  #ifndef CPP_EXAMPLE_H

  #define CPP_EXAMPLE_H

  extern "C" int add( int x, int y );

  #endif

  //C++實現檔案 cppExample.cpp

  #include "cppExample.h"

  int add( int x, int y )

  {

  return x + y;

  }

  /* C實現檔案 cFile.c

  /* 這樣會編譯出錯:#include "cExample.h" */

  extern int add( int x, int y );

  int main( int argc, char* argv[] )

  {

  add( 2, 3 );

  return 0;

  }

相關推薦

C程式設計使用到的重要關鍵字之一extern

extern: extern可以置於變數或者函式前,以表示變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他模組中尋找其定義。另外,extern也可用來進行連結指定。 在一個原始檔裡定義了一個陣列:char a[6];   在另外一個檔案裡用下列語

C程式設計使用到的重要關鍵字之一const

  1)、這個問題討論的是“常量”與“只讀變數”的區別。常量肯定是隻讀的,例如5, "abc",等,肯定是隻讀的,因為常量是被編譯器放在記憶體中的只讀區域,當然也就不能夠去修改它。而“只讀變數”則是在記憶體中開闢一個地方來存放它的值,只不過這個值由編譯器限定不允許被修改。C語言關鍵字const就是用來限定一個

C++重要關鍵字(包含借鑑其他博主的東西)

//記憶體 棧區 與 static區 ,C++為了相容C,#include 只是文字替換,導致一堆名稱空間之間的複雜問題,倍受人詬病 extern  關鍵字 1.基本含義:意如其名,告訴編譯器宣告的東西是外部的。 特殊用法  extern "C" + 函式,宣告這個函式翻譯優化的時候

Linux重要知識點

1.對於一個需求:一個專案組有好幾個使用者,所有使用者在目錄中建立檔案,可以刪除自己的檔案,但不能刪除別人的檔案,它的實現方法如下: 沾滯位:首先知道沾滯位是針對目錄來設定的。 ——許可權位 實現方法: chmod +t file:設定目錄的沾滯位 chmod

微信小程式,開發重要的知識點(加密解密,轉發,進入場景,session_key)

小程式的授權資訊:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html   小程式的系統引數和進入場景引數等:https://developers.weixin.qq.com/

微積分重要的不等式:Jensen不等式、平均值不等式、Holder不等式、Schwarz不等式、Minkovski不等式 及其證明

一:幾個重要不等式的形式 1,Jensen不等式 2,平均值不等式 3,一個重要的不等式 4,Holder不等式 5,Schwarz不等式 和 Minkovski不等式

MySQL JDBC URL重要引數說明

http://www.cnblogs.com/yokoboy/archive/2013/03/01/2939315.html jdbc:mysql://[host:port],[host:port].../[database][?引數名1][=引數值1][&引數名2

Mybatis重要

本文基於Mybatis3.2.0版本的程式碼。 1.org.apache.ibatis.mapping.MappedStatement MappedStatement類在Mybatis框架中用於表示XML檔案中一個sql語句節點,即一個<select />、&

(三)(1) Z-Stack協議重要概念的理解

     PANID的出現一般是伴隨在,確定通道以後的。PANID其全稱是Personal Area Network ID,網路的ID(即網路識別符號),是針對一個或多個應用的網路,用於區分不同的ZigBee網路,一般是mesh或者cluster tree兩種拓撲結構之一。所有節點的panID唯一,一個網路

C語言預定義巨集

顧名思義,預定義巨集就是已經預先定義好的巨集,我們可以直接使用,無需再重新定義。ANSI C 規定了以下幾個預定義巨集,它們在各個編譯器下都可以使用:__LINE__:表示當前原始碼的行號;__FILE__:表示當前原始檔的名稱;__DATE__:表示當前的編譯日期;__TI

c++比較不常用的關鍵字

我們在編寫應用程式的時候explicit關鍵字基本上是很少使用,它的作用是"禁止單引數建構函式"被用於自動型別轉換,其中比較典型的例子就是容器型別,在這種型別的建構函式中你可以將初始長度作為引數傳遞給建構函式.例如:你可以宣告這樣一個建構函式class Array{public: explicit Array

Servlet重要的對象(轉)

localhost http ttr 屬性 webapps source 指定路徑 開始 orm  講解四大類,ServletConfig對象,ServletContext對象、request對象,response對象 ServletConfig對象         獲取途

圖像處理基本的處理方法c#代碼實現

位圖 edi windows系統 process 圖案 電視 間接 做了 同步 圖像是人類獲取和交換信息的主要來源,因此,圖像處理的應用領域必然涉及到人類生活和工作的方方面面。隨著人類活動範圍的不斷擴大,圖像處理的應用領域也將隨之不斷擴大。(1)航天和航空技術方面的應用 數

C# 關鍵詞的使用

C#關鍵字對於C#中幾個關鍵詞老是容易搞混淆,在於記錄一下 一 outout 關鍵字可以將值類型轉換成引用類型,帶入到方法中,並進行返回。static void Main(string[] args){ Test2(out int b); Console.WriteLine(b

C遞迴問題

1. 計算累和 1+2+3+……+n #include<stdio.h> #include<stdlib.h> #include<string.h> int add_up(int n){ if(n==1){ return 1;

webservice 教程學習系列(四)——webservice 比較重要的術語

(1)wsdl:webservice definition language(直譯webservice定義語言) 1.對應一種型別檔案.wsdl 2.定義了webservice的伺服器端和客戶端應用互動傳遞請求和響應資料的格式和方式; 3.一個webservice對應一個wsdl文件;

Altium Designer 19對output job file檔案進行設定的重要步驟

1.在已開啟的當前專案中,執行選單命令(檔案(File)→New(新的)→output job file),則會建立一個新的文件輸出檔案。 2、 選中需要列印的文件,用滑鼠右鍵點選,在彈出的選單中選擇Page Setup,則出現進行文件列印所需設定的對話方塊,在對話方塊裡選擇列印任務的紙

const關鍵字的用法,在C++程式設計要儘可能用const

為什麼說在C++程式設計中要儘可能用const呢? 因為這樣可以獲得編譯器的幫助,以便寫出健壯性的程式碼。 C++ const 允許指定一個語義約束,編譯器會強制實施這個約束,允許程式設計師告訴編譯器某值是保持不變的。如果在程式設計中確實有某個值保持不變,就應該明確使用c

App效能測試重要概念

我們在使用各種 App 的時候基本會關注到:這款軟體挺耗流量的?執行起來裝置掉電有點快嘛?切換頁面的時候還會有卡頓等現象?如果遇到有這些問題的 App 我們基本會將它請出我們的 我們在使用各種 App 的時候基本會關注到:這款軟體挺耗流量的?執行起來裝置掉電有點快嘛?切換頁面的時候還會有卡頓等現

Java執行緒池(2)——執行緒池重要方法詳解

【內容摘要】 在java中,如果需要進行多執行緒程式設計,可以採用java自帶的執行緒池來實現,執行緒池對於我們新手來說是一個非常好的選擇,因為我們可以不用關心執行緒池中執行緒是如何排程的,避免在多執行緒程式設計過程產生死鎖等問題。在瞭解執行緒池的使用前,本文