C潛規則篇之防止重定義

2021-06-19 16:53:55 字數 2521 閱讀 5566

c程式編譯時常出現類似*** redefinition錯誤,除了模組間的命名衝突(命名汙染及static),問題多數與標頭檔案管理有關。大型c工程的標頭檔案管理很麻煩:c原始檔往往包含很多標頭檔案,標頭檔案又包含其他標頭檔案,形成複雜的巢狀包含;c沒有嚴格限定原始檔和標頭檔案的功能邊界,二者都可以包含全域性變數和函式等實體定義。這都可能導致型別或實體定義被重複包含和展開,使編譯器丟擲重定義錯誤。

解決重定義問題分三部分,多數人只知其一而不知其二和其三:

其一,用條件編譯(

標頭檔案衛士

)防止標頭檔案重複包含

假設原始檔test.c中包含a.h和b.h兩個標頭檔案,而a.h和b.h裡又都包含另乙個標頭檔案x.h(很常見),那麼x.h就會被test.c兩次include,如果x.h裡定義了某結構體,如:

typedef struct

test

預處理(見c編譯過程)後,test.c裡包含兩個struct test定義,編譯器就會報重定義錯誤。乙個巧妙辦法是套用下面標頭檔案模板(俗稱標頭檔案衛士):

#ifndef _hdrname_h   //_hdrname_h按標頭檔案的檔名取名,防止同名衝突

#define _hdrname_h

…… (content of header file)

#endif

當頭檔案第一次被包含,_hdrname_h還未define,#ifndef條件滿足,預處理器進入#ifndef和#endif之間,_hdrname_h被正式define,標頭檔案內容也得到處理。當再次被包含,由於_hdrname_h已定義,開頭的#ifndef不再滿足,標頭檔案內容被直接忽略。這樣防止因標頭檔案重複包含引起的型別重定義錯誤。這種做法基本算是c的江湖標準了。

其二,在c原始檔裡定義全域性變數與函式,不要在標頭檔案裡定義

#ifndef能防止標頭檔案重複包含導致的編譯階段型別重定義錯誤,卻無法防止標頭檔案中的全域性變數和函式定義導致的鏈結階段實體重定義錯誤。例如:

/************main.c************/

#include "test.h"

void main()

/********** test.h**********/

#ifndef _test_h_

#define _test_h_

char str1 = "char1";

char str2 = "char2";

#endif

/*********test1.c***********/

#include "test.h"

extern char str1;

void test1()

/*********test2.c************/

#include "test.h"

extern char str2;

void test2()

上面情形,有些編譯器報warn,有些可能出現str1和str2重定義error,概念不清的人可能會問:test.h已用#ifndef防止重包含,為什麼還有重定義?

這其實是另乙個問題,錯誤根源在於test.h裡包含變數/函式等占用記憶體的實體元素,而不僅僅是define/struct/union等虛型別。雖然用#ifndef防止test.h重複包含,但注意test1.c和test2.c中都包含test.h,預處理器會把test.h分別附到兩個原始檔開頭,相當於在test1.c和test2.c中重複定義了str1,str2兩個全域性變數。編譯完開始link時,linker會發現test1.obj和test2.obj中都有str1,str2兩個符號,於是報錯,這跟c命名衝突是同一情況。

解決辦法是在.c檔案中定義全域性變數,然後建乙個包含所有全域性變數extern宣告的標頭檔案,其他所有使用這些變數的.c檔案中都要包含這個標頭檔案。如下:

/*****main.c*****/

#include "test.h"

char str1 = "char1";

char str2 = "char2";

void main()

/***** test.h*****/

#ifndef _test_h_

#define _test_h_

extern char str1;

extern char str2;

#endif

/*****test1.c*****/

#include "test.h"

void test1()

/*****test2.c*****/

#include "test.h"

void test2()

在標頭檔案中定義函式,錯誤現象和原因類似。因此標頭檔案中可以包含型別定義和實體宣告,不應該包含實體定義。另外,有時遺漏typedef也會導致類似重定義問題:

typedef structtest_s;

如果遺漏struct前的typedef,test_s就變成無名結構體變數而不是原來的自定義型別,放在標頭檔案裡也會出錯。

工作手記之潛規則篇

1.mysql的timestamp中的一些潛規則 timestamp列的預設值是current timestamp常量值。當記錄資料發生變化的時候,timestamp列會自動將其值設定為current timestamp。可以自動更新時間了,不錯 乙個表可以存在多個timestamp列,但只有乙個列...

C 之過載重寫重定義

概念 函式有同樣的名稱,但是引數列表不相同的情形,這樣的同名不同引數的函式之間,互相稱之為過載函式。基本條件 注意 概念 也稱為覆蓋,子類重新定義父類中有相同名稱和引數的虛函式,主要在繼承關係中出現。基本條件 注意 驗證程式 123 4567 891011 1213 1415 1617 1819 2...

c 之 過載 重寫 重定義

函式過載 必須在同乙個類中進行 子類無法過載父類的函式,父類同名函式將被子類名稱覆蓋 過載是在編譯期間根據引數型別和個數決定函式的呼叫 靜態聯編 函式重寫與重定義 重寫 必須發生在基類和派生類之間,並且父類與子類的函式必須有完全相同的函式原型 使用virtual關鍵字宣告之後能夠產生多型 如果不使用...