Chinaunix首页 | 论坛 | 博客
  • 博客访问: 848013
  • 博文数量: 756
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 4980
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:40
文章分类

全部博文(756)

文章存档

2011年(1)

2008年(755)

我的朋友

分类:

2008-10-13 16:14:13

楔子:
  本文主要描述了把第三方代码移植到某一目标平台过程中所发生的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 
#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 
#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 
#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 
#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 
#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代码就更头疼了。唉,这年头混口饭吃不容易阿~~
-------------
乾坤一笑 写于2005年8月6日  转载请标明出处和原文链接
--------------------next---------------------
试了一下你说的方法,是可以的。我准备加为方法五。代码如下:
//filename: b.h
#ifndef B_H
#define B_H

#define INT32 B_INT32
typedef long INT32;


void b_bar(INT32 num);
#undef INT32

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

#include 
#include "b.h"

#define INT32 B_INT32
void b_bar(INT32 num)
{
	printf("b_bar trace: %d\n", num);
}
#undef INT32
只需要修改B代码中相应的位置就可以了。起初偶也考虑使用这种方法。但是这个方法不切合实际的地方就是要把所有b.c中都对应的加上#define INT32 B_INT32和#undef INT32这种宏。而b.c这种文件特别多,几百个是有的,关系有特别复杂,我们是移植B代码,不便对其结构大做改动,以后B代码升级了我们也不好同步阿。
--------------------next---------------------
比如INT,当然不可能保证是32bits的。但是定义成INT32就基本上可以保证了。
--------
==》 我不同意你这个观点。当然,从理论上说确实是这样的。但是,理论不是实际。实际的情况是,使用诸如INT32这种类型定义的代码亦然是把它当作long或者short等不可移植的固定类型来处理。就是说具体实现的很多地方都没有遵从理论的约束。实际上我们自己做开发的时候也是这样,你不能要求每一个开发人员都能高瞻远瞩,严格按照理论标准办事,这样的开发团队,估计也就贝尔实验室这种地方才供养的起。因为拥有A公司和第三方公司的全部源码,所以我已经看到了很多这种实现和理论不一直的地方。但是我是做porting,不可能大幅度修改B代码中的不完善的地方;另外,很多其他人也在做其他第三方代码的porting,我也不能给它们这种无法具体执行的指导意见。:)
--------------------next---------------------
可否如下:
#include "b.h"
#undef INT32

#include "a.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
}

也就是我不是移植B到A,而是在A里把B作为一个已经适合的库使用。也就它本身是符合A平台运行要求的,是先有B后有A,那么在A使用B是,不要引入冲突(这里是取消原类型影响);只有当B的代码不能运行于A平台时,考虑修改他的类型(比如:原INT32是int,但是现在int是64位了,INT32应该是short,那么需要修改B类型。一般它应该是一个整体,把所有移植相关的类型会放在一个类型定义文件的)。
--------------------next---------------------

阅读(980) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~