第三方C代碼移植過程中的typedef重複定義問題

楔子:

本文主要描述了把第三方代碼移植到某一目標平臺過程中所發生的typedef重複定義的問題。之所以要寫這個問題,原因有三:1>這個問題是一個移植代碼過程中經常遇到的老大難問題2>我還沒有完全(或者說完美的)解決這個問題3>想得到諸位高手的點撥。

首先要看看下面的原始代碼(爲了突出重點,我簡化了代碼結構。實際代碼要複雜的多):

a.c和a.h代表主開發平臺的代碼,下面簡稱爲A平臺代碼;b.c和b.h代表要移植到A平臺的第三方的代碼,下面簡稱爲B代碼。

<代碼片斷I>
//filename: b.h
#ifndef B_H
#define B_H

typedef long INT32;

void b_bar(INT32 num);

#endif //B_H
//filename: b.c

#include <stdio.h>
#include "b.h"

void b_bar(INT32 num)
{
 printf("b_bar trace: %d\n", num);
}


//filename: a.h
#ifndef A_H
#define A_H

typedef short INT32;

void a_foo(INT32 num);

#endif //A_H
//filename: a.c
#include <stdio.h>
#include "a.h"

void a_foo(INT32 num)
{
 printf("a_foo trace: %d\n", num);
}

int main()
{
 INT32 abc=100;
 a_foo(abc);
}

源代碼中A代碼和B代碼中並無任何瓜葛。A代碼和B代碼中都定義了一個叫做INT32的類型,但是在A代碼中INT32是以short爲原型的,就是說A代碼中所有使用INT32的類型都是期望使用short的。同理,B代碼中的所有使用INT32類型的地方都是期望使用long的。因此,我們只要在移植過程中保證A代碼和B代碼中的類型定義都滿足自己原始的期望,那麼這個移植過程中類型定義就是成功的,代碼運行起來也就會很穩定;反之,移植的代碼就存在着極大的風險。

現在進入正題。我們想在a.c中使用B代碼中的b_bar()這個函數,常規的方法是這樣的:

<代碼片斷II>
//filename: a.c
#include <stdio.h>
#include "a.h"

//////// Add this include sentence,We want use B 's declare
#include "b.h" 

void a_foo(INT32 num)
{
 printf("a_foo trace: %d\n", num);
}

void a_xxx(INT32 num)  ///////// Add this function
{
 printf("a_xxx trace: %d\n", num);
 b_bar(num);   ///////// Call b_bar() function
}

int main()
{
 INT32 abc=100;
 a_foo(abc); 
 a_xxx(abc);  ///////// Use a_xxx() function
}
但是,我們代碼如果這麼使用的話,就會出錯。錯誤的原因大概是:redefinition; different basic types。重複定義,不一致的基本類型。因爲A代碼和B代碼中都定義了INT32這個類型。

那麼我們應該如何搞定?有大概三種方法來解決這個問題,我們一一來看。


一、extern聲明法

這種方法的基本思想是講問題由編譯階段推遲到鏈接階段。我一般會優先使用這種方法,代碼如下:

<代碼片斷III>
//filename: a.c
#include <stdio.h>
#include "a.h"

///Use extern declare instead of include sentence, and use long instead of INT32
extern void b_bar(long num);

void a_foo(INT32 num)
{
 printf("a_foo trace: %d\n", num);
}

void a_xxx(INT32 num)  ///////// Add this function
{
 printf("a_xxx trace: %d\n", num);
 b_bar(num);   ///////// Call b_bar() function
}

int main()
{
 INT32 abc=100;
 a_foo(abc); 
 a_xxx(abc);  ///////// Use a_xxx() function
}
如上面代碼所示,我不再在A代碼裏面include B代碼的.h文件,這樣就避免了typedef的重複定義。同時,我修改了extern的b_bar()的聲明,使其使用C語言基本類型long,這樣就滿足了B代碼的原始期望,B代碼運行起來就不會有錯。通過extern,將問題推遲到鏈接期,鏈接的過程中是以原始類型爲基準的,所以鏈接起來也不會出錯。基本上就解決了這個問題。


方法一的缺點:1>修改extern的B代碼的聲明中的類型爲基本類型,看起來代碼不是很優雅;2>如果A代碼中多處使用B代碼,或者要使用B代碼中的多個函數,大量的extern將使代碼看起來非常的噁心。


二、統一typedef法

我所使用的X公司的源代碼實際上也是有好幾個部分拼接而成的,每個部分都有自己的typedef。它的代碼中通過如下方法來規避typedef重複定義的問題:

<代碼片斷IV>
//filename: b.h
#ifndef B_H
#define B_H


#ifndef A_H
typedef long INT32;
#endif

void b_bar(INT32 num);

#endif //B_H
修改b.h,而b.c、a.h 使用代碼片斷1的,a.c使用代碼片斷2的。


這樣一來,B代碼在b.h進行typedef的時候,如果檢測到目前代碼中已經存在了INT32這個定義,就不在進行這個定義了,就使用現存的INT32這個定義。這樣一來,其實B代碼在使用short來替代INT32,而不是它的原始期望中的long。我個人認爲,這會導致B代碼中的某些實現不穩定、出錯。所以在移植第三方代碼過程中,我絕對不推薦這種用法。(這是開討論會中爭議最大的地方,如果你有金磚,千萬不要吝惜,拍過來吧~~讓暴風雨來的更猛烈些吧)


三、類型適配法

這段文字已經不是再解決上面的問題了,純粹是做爲一種引申的思路而已。某些公司的代碼會提供自己名字前綴的typedef,如ABC公司的類型定義就是ABC_INT32,而XXX公司的類型定義就是XXX_INT32。這樣就能保證自己的類型定義總是在自己內部使用,而不至於引起衝突來。


<代碼片斷V>
//filename: b.h
#ifndef B_H
#define B_H

typedef long B_INT32;

void b_bar(B_INT32 num);

#endif //B_H
//filename: b.c

#include <stdio.h>
#include "b.h"

void b_bar(B_INT32 num)
{
 printf("b_bar trace: %d\n", num);
}


修a.h 使用代碼片斷1的,a.c使用代碼片斷2的。


這樣就不會在A代碼和B代碼之間引起類型定義衝突了。如果類型定義複雜了,有可能在A和B之間要進行強制類型轉換,這點的確不爽。不過一般B代碼會引入一個類型適配層,用typedef來把A和B之間的類型建立等價關係,這樣就一點問題也沒有了。
//filename: b_type_adapt.h
typedef B_INT32 A_INT;
typedef B_UINT32 A_UINT;


在A代碼中使用B代碼的時候就include這個b_type_adapt.h,這樣A就能正確識別出B的類型來。問題就完全解決了。


總結:遺憾的是,有很多公司的代碼都不是按照方法3來做的,它們都認爲自己比較牛,自己應該定義原始類型,%^&(×)#%¥^&…… 唉,就苦了我們porting engineer了。因爲是做porting,所以我不能把B中的typedef的類型做個全文替換,這樣以後升級B代碼就更頭疼了。唉,這年頭混口飯喫不容易阿~~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章