TypeInfo模块是Doom3中用于动态类型识别(RTTI)和辅助查错的模块,可以检测类中未初始化变量、打印某种类型的对象中所有成员变量的值,或打印整个程序中所有实体对象(idEntity)的值。在写程序过程中,可能经常会出现一些由于未对成员变量初始化而导致的问题。而这类问题一般在Debug模式下没有问题,在Release版本中偶尔出现,这导致很难定位与问题相关的代码。
除了检测未初始化的内存,其他方面的应用,可以从TypeInfo.h中的几个函数可以略窥一二。
/*
===================================================================================
Game Type Info
===================================================================================
*/
// 根据偏移取得变量名
const char * GetTypeVariableName( const char *typeName, int offset );
// 打印某个对象的信息
void PrintType( const void *typePtr, const char *typeName );
// 将某对象值写入文件
void WriteTypeToFile( idFile *fp, const void *typePtr, const char *typeName );
// 给某对象的所有变量设定一个初始值
void InitTypeVariables( const void *typePtr, const char *typeName, int value );
// 列举所有类型信息
void ListTypeInfo_f( const idCmdArgs &args );
// 将所有游戏状态(所有idEntity信息)写入文件
void WriteGameState_f( const idCmdArgs &args );
// 比较游戏状态
void CompareGameState_f( const idCmdArgs &args );
// 测试保存游戏
void TestSaveGame_f( const idCmdArgs &args );
经过几天的努力,我将TypfInfo相关的代码提取出,单独创建了工程文件,并做了简单的代码演示。有爱好的同学可以深入研究。点击这里下载。在工程(
vs2010) 中,Example是一个简单的演示程序,idLib是对Doom3源码中idLib的删减版本。TypeInfo是生成GameTypeInfo.h类型信息的控制台工具。
TypeInfo的用法为 typeinfo.exe [source_code_path] [out_file_name]
实现:
TypeInfo检测未初始化内存实现原理为,对代码中所有的头文件扫描分析,并将类型中,变量名,变量类型等信息记录生成一个GameTypeInfo.h文件。然后在new一个对象时,会先将这个对象的内存初始化为0xcdcdcdcd。当对象构造完成时,检测对象内存中是否有某值为0xcdcdcdcd,如果有,就通过GameTypeInfo.h中记录的类型,变量名,偏移等信息将未初始化的变量信息打印出来。
将上述过程分为三段内容:1. 解析 2. 生成类型信息 3. 利用类型信息
1. 解析:
基本的词法解析在idLib中实现,相关类为idToken, idLexer, idParser。
2. 生成类型信息
idTypeInfoGen利用idParser来解析代码头文件,记录相关类型信息,类型信息包括:常量,枚举量和类(包括结构体)
// 常量信息
typedef struct {
const char * name;
const char * type;
const char * value;
} constantInfo_t;
// 枚举形变量信息
typedef struct {
const char * name;
int value;
} enumValueInfo_t;
// 枚举类型信息
typedef struct {
const char * typeName;
const enumValueInfo_t * values;
} enumTypeInfo_t;
// 类成员变量信息
typedef struct {
const char * type;
const char * name;
int offset;
int size;
} classVariableInfo_t;
// 类的类型信息(包括结构体)
typedef struct {
const char * typeName;
const char * superType;
int size;
const classVariableInfo_t * variables;
} classTypeInfo_t;
所有信息记录在GameTypeInfo.h中,详细内容可以查阅源代码。
3. 对类型信息的利用
3.1 通过类型名创建实例。在Doom3程序的GamePlay层,主要的类都是从idClass继承下来的,并且在类声明和定义时,通过idTypeInfo记录类型信息,包括记录类之间的父子关系。在类中加入宏CLASS_PROTOTYPE, CLASS_DECLARATION来实现。
创建类实例时是这样的:
idTypeInfo *cls;
idClass *obj;
cls = idClass::GetClass( "idPlayer" );
obj = cls->CreateInstance();
3.2 输出未初始化变量的名称。
idClass中重载了new操作符,以便记录内存的使用量和创建的对象个数,如果需要检测未初始化的内存,还会将内存初始化为0xcdcdcdcd。
void * idClass::operator new( size_t s ) {
int *p;
s += sizeof( int );
p = (int *)Mem_Alloc( s );
*p = s;
memused += s;
numobjects++;
#ifdef ID_DEBUG_UNINITIALIZED_MEMORY
unsigned long *ptr = (unsigned long *)p;
int size = s;
assert( ( size & 3 ) == 0 );
size >>= 2;
for ( int i = 1; i < size; i++ ) {
ptr[i] = 0xcdcdcdcd;
}
#endif
return p + 1;
}
对象创建后,调用FindUninitializedMemory来检测未初始化的内存
/*
================
idClass::FindUninitializedMemory
================
*/
void idClass::FindUninitializedMemory( void ) {
#ifdef ID_DEBUG_UNINITIALIZED_MEMORY
unsigned long *ptr = ( ( unsigned long * )this ) - 1;
int size = *ptr;
assert( ( size & 3 ) == 0 );
size >>= 2;
ptr = ( unsigned long * )this;
for ( int i = 0; i < size; i++ ) {
if ( ptr[i] == 0xcdcdcdcd ) {
const char *varName = GetTypeVariableName( GetClassname(), i << 2 );
idLib::Warning( "type '%s' has uninitialized variable %s (offset %d)", GetClassname(), varName, i << 2 );
}
}
#endif
}
这里通过 GetTypeVariableName 在类型信息表中查找变量名称。
3.3打印输出某个实例的所有成员变量值。
代码实现在TypeInfo.cpp文件中,没有仔细研究这段代码实现,就不在这里单列了。