深入分析C 中宣告與定義的區別

2021-07-27 07:08:07 字數 4401 閱讀 5654

c++學了這麼多年你知道為什麼定義類時,類的定義放在.h檔案中,而類的實現放在cpp檔案中。它們為什麼能夠關聯到一起呢?你知道什麼東西可以放在.h檔案中,什麼不能。什麼東西又可以放在cpp檔案中。如果你忘記了或是壓根就不明白,那麼讀過此文你會清晰無比!!

首先談下宣告與定義的區別。

宣告是將乙個名稱引入程式。定義提供了乙個實體在程式中的唯一描述。宣告和定義有時是同時存在的。

如int a;

extern int b=1;

只有當extern中不存在初始化式是才是宣告。其他情況既是定義也是宣告。

但是在下列情況下,宣告僅僅是宣告:

1:僅僅提供函式原型。如void func(int,int);

2: extern int a;

3:class a;

4:typedef宣告

5:在類中定義的靜態資料成員的宣告

如:

class a 

;

下列情況下 ,定義僅僅是定義:

1:在類定義之外,定義並初始化乙個靜態資料成員。如 a::a=0;

2:在類外定義非內聯成員函式。

宣告僅僅是將乙個符號引入到乙個作用域。而定義提供了乙個實體在程式中的唯一描述。在乙個給定的定義域中重複宣告乙個符號是可以的,但是卻不能重複定義,否則將會引起編譯錯誤。但是在類中的成員函式和靜態資料成員卻是例外,雖然在類內它們都是宣告,但是也不能有多個。

如:明白了宣告與定義的區別,還需要明白 內部鏈結、外部鏈結。只有明白了它們你才會知道開頭提出的問題。

在編譯時,編譯器只檢測程式語法和函式、變數是否被宣告。如果函式未被宣告,編譯器會給出乙個警告,但可以生成目標檔案。而在鏈結程式時,鏈結器會在所有的目標檔案中找尋函式的實現。如果找不到,那到就會報鏈結錯誤碼(linker error)。在vc下,這種錯誤一般是:link 2001錯誤,意思說是說,鏈結器未能找到函式的實現。

鏈結把不同編譯單元產生的符號聯絡起來。有兩種鏈結方式:內部鏈結和外部鏈結。

如果乙個符號名對於它的編譯單元來說是區域性的,並且在鏈結時不可能與其他編譯單元中的同樣的名稱相衝突,那個這個符號就是內部鏈結。內部鏈結意味著對此符號的訪問僅限於當前的編譯單元中,對其他編譯單元都是不可見的。

static關鍵字作用在全域性變數時,表示靜態全域性變數。但是作用域僅僅在當前檔案作用域內。其他檔案中即使使用extern宣告也是無法使用的。const也類似。

帶有static、const關鍵字和列舉型別的連線是內部的。

具有內部鏈結的符號無法作用於當前檔案外部,要讓其影響程式的其他部分,可以將其放在.h檔案中。此時在所有包含此.h檔案的原始檔都有自己的定義且互不影響。

類的定義具有內部鏈結,由於它是定義,因此在同一編譯單元中不能重複出現。如果需要在其他編譯單元使用,類必須被定義在標頭檔案且被其他檔案包含。僅僅在其他檔案中使用class a;宣告是不行的,原因就是類的定義是內部鏈結,不會在目標檔案匯出符號。也就不會被其他單元解析它們的未定義符號。理解這一點很重要。

內聯函式也具有內部鏈結。

在乙個多檔案的程式中,如果乙個符號在鏈結時可以和其他編譯單元互動,那麼這個名稱就有外部鏈結。外部鏈結意味著該定義不僅僅侷限在單個編譯單元中。它可以在.o檔案中產生外部符號。可以被其他編譯單元訪問用來解析它們未定義的符號。因此它們在整個程式中必須是唯一的,否則將會導致重複定義。

非內聯成員函式、非內聯函式、非靜態自由函式都具有外部鏈結。

內聯函式之所有具有內部鏈結,因為編譯器在可能的時候,會將所有 對函式的呼叫替換為函式體,不將任何符號寫入.o檔案。

判斷乙個符號是內部鏈結還是外部鏈結的乙個很好的方法就是看該符號是否被寫入.o檔案。

前面說的是定義對鏈結方式的影響,接下來說下宣告對鏈結方式的影響。

由於宣告只對當前編譯單元有用,因此宣告並不將任何東西寫入.o檔案。

如extern int a;

int func();

這些宣告本身不會影響到.o檔案的內容。每乙個都只是命名乙個外部符號,使當前的編譯單元在需要的時候可以訪問相應的全域性定義。

函式呼叫會導致乙個未定義的符號被寫入到.o檔案。如果a在該檔案中沒有被使用,那麼沒有被寫入到.o檔案。而func函式有對此函式的呼叫。也就會將此符號寫入目標檔案。此後此.o檔案與定義此符號的.o檔案被連線在一起,前面未定義的符號被解析。

上述宣告有可能導致該符號被寫入目標檔案中。但是以下宣告並不會導致該符號寫入到目標檔案中。

如:

typedef int int;

class a;

struct s;

union point;

它們的鏈結也是內部的。

類宣告和類定義都是內部鏈結。只是為當前編譯單元所用。

靜態的類資料成員的定義具有外部鏈結。如

class a 

靜態資料成員a僅僅是乙個宣告,但是它的定義a::a=0;卻具有外部鏈結。

c++對類和列舉型別的處理方式是不一樣的。比如:在不定義類時可以宣告乙個類。但是不能未經定義就宣告乙個列舉型別。

基於以上的分析,我們可以知道:將具有外部鏈結的定義放在標頭檔案中幾乎都是程式設計錯誤。因為如果該標頭檔案中被多個原始檔包含,那麼就會存在多個定義,鏈結時就會出錯。

在標頭檔案中放置內部鏈結的定義卻是合法的,但不推薦使用的。因為標頭檔案被包含到多個原始檔中時,不僅僅會汙染全域性命名空間,而且會在每個編譯單元中有自己的實體存在。大量消耗記憶體空間,還會影響機器效能。

const和static修飾的全域性變數僅僅在當前檔案作用域內有效。它們具有內部鏈結屬性。

下面列出一些應該或是不應該寫入標頭檔案的定義:

#ifndef test_h

#define test_h

int a; //a有外部鏈結,不能在標頭檔案中定義。

extern int b=10;//同上。

const int c=2;//c具有內部鏈結,可以定在標頭檔案中但應該避免。

static int d=3;//同上。

static void func(){} //同上。

void func2(){} //同a。

void func3();//可以。僅僅是宣告。並不會導致符號名被寫入目標檔案。

class a

; a::e=10;//不可以在標頭檔案中包含具有外部鏈結的定義。符號名別寫入目標檔案。

void a:func4()//不可以,類成員函式。外部連線。

#endif

相信大家現在明白為什麼只在型別宣告成員函式,而不實現它是合法的了。也可以回答為什麼類的定義可以放在.h檔案中。而類的實現可以放在同名的cpp檔案中。老師以前的介紹是說編譯器會自動尋找同名的cpp檔案。其實是因為由於cpp檔案中儲存的是成員函式的實現,而成員函式具有外部鏈結特性,會在目標檔案產生符號。在此檔案中此符號是定義過的。其他呼叫此成員函式的目標檔案也會產生乙個未定的符號。兩目標檔案連線後此符號就被解析。注意static資料成員應該放在cpp檔案中。而不能放在.h檔案。

有內部鏈結的定義可以定義在cpp檔案中,並不會影響全域性的符號空間 。但是在cpp檔案作用域中要避免定義(並不禁止)沒有宣告為靜態的資料和函式,因為它們具有外部鏈結。

int a; 

void func()

上述定義具有外部鏈結可能會與全域性命名空間的其他符號名稱存在潛在衝突。如果確實需要使用全域性的變數或函式。可以為它們加上static關鍵字。使其作用域侷限在當前檔案內,具有內部鏈結也就不會對全域性命名空間產生影響。因為內聯函式和靜態自由函式、列舉以及const型別的資料都具有內部鏈結,所以它們可以定義在cpp檔案中,而不會影響全域性命名空間。

typedef和巨集定義不會將符號引入.o檔案,它們也可以出現在cpp檔案中,不會影響全域性命名空間。

typedef 為乙個已存在的型別建立乙個別名。而不是建立乙個新的型別。它不提供型別安全。如

typedef int inta; 

typedef int inb;

在需要inta的地方使用intb是不會報錯的。它們可以互相替換。因為此我們稱它不提供型別安全。但是在定義函式型別時typedef經常使用,可以使定義更清晰。

標準c庫提供乙個assert巨集,用以保證給定的表示式值非零。否則便會輸出錯誤資訊並終止程式執行。只有在程式中沒有定義ndebug時,assert才會工作。一旦定義ndebug  ,assert語句將會被忽略 。注意與vc中的assert相區別。assert是vc提供的。當_debug被定義時才會起作用。

在vc的debug模式下_debug會被定義。而在release模式下ndebug會被定義。

以上內容參考自《large scale c++ software design》。

C 中宣告與定義的區別

宣告 一種把乙個名稱引入或者重新引入到某個c 作用域的構造。定義 它也是一種宣告,但該宣告必須給出被宣告實體的細節。對於變數而言,這裡的細節是指 為被宣告實體保留儲存空間。對於class型別和函式定義而言,指的是包含有一隊花括號內容的宣告。對於外部變數而言,指的是前面沒有關鍵字extern或者在宣告...

C 中定義與宣告的區別

c 中的定義與宣告是兩個很容易混淆的概念,而且對於初學者來說,必須要區分兩個概念的不同。乙個變數的定義是用於為變數分配儲存空間。也可以為變數指定初始值。也就是說,定義不一定要有初始值,但是一定要分配儲存空間。而乙個變數的宣告則是說明乙個變數的型別以及名字。定義也是宣告,讀者可以這樣想,如果乙個變數要...

深入分析執行緒與程序的區別

每個程序和執行緒都會通過task struct 這麼乙個結構體去維護 這個結構體主要就是儲存著程序或執行緒在記憶體排程所使用的資源 struct task struct 於是 我們就有了這麼一張圖 mm struct每個程序都會維護乙份屬於自己乙個結構體 然而每個執行緒都會共享程序的這個mm str...