楔子:
本文主要描述了把第三方代码移植到某一目标平台过程中所发生的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);
}
本文主要描述了把第三方代码移植到某一目标平台过程中所发生的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;
#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代码就更头疼了。唉,这年头混口饭吃不容易阿~~