原文:点击打开链接

DOOM3代码放出来也好久了,过年的时候抽空看了看,先更新第一篇,主要是编译的一些问题笔记,以及用自动生成的TypeInfo表进行未初始化变量检查的机制分析。

编译的问题

Can’t load default.cfg

需要手动在FileSystem.cpp中把下面这行fs_basepath初始化改成自己的目录:

idCVar idFileSystemLocal::fs_basepath( "fs_basepath", "Z:\\Docs\\Project_X\\Ref_Engines\\Doom3", CVAR_SYSTEM | CVAR_INIT, "" );

这时因为在编译gamex86.dll时,会先执行一次TypeInfo的生成,这时会用默认路径去加载default.cfg。

win_input.cpp里面包含了一些Unicode的字符串,中文系统下的VS2010编译有这些字符的文件时,会把当前系统字符集(比如gb2312)无法表示的Unicode字符自动替换成“?”。导致编译失败,手动修改一下就好了。不过VS这种行为还是很奇怪的

最新代码把移除的阴影渲染代码又加回去了,不过要做一些小修改,才能在VS中编译

1.在glext.h中把__STDC_VERSION__定义为0(非GCC编译器)

2.在TypeInfo.cpp里面会在预编译头前面强行把private和protected重新定义成public,以方便进行变量初始化和检查工作。不过最新代码显然把一些文件漏了,所以会出现无法访问private变量的编译错误,手动把这些类全部改成public即可。。。

// This is real evil but allows the code to inspect arbitrary class variables.
#define private public
#define protected public
#include "../../idlib/precompiled.h"

3.从VS启动游戏的设置

在Debugging面板中

设置Command为:$(OutDir)DOOM3.exe,不能用默认的$(TargetPath)

否则gamex86.dll不能正常加载.这个问题很是奇怪,宏展开两者的结果应该是一样的。

设置Command Line Arguments:

+set fs_basepath "Z:\Docs\Ref_Engines\Doom3" +set com_allowConsole 1 +set si_pure 0

Doom3的内存调试机制分析

C/C++引擎的特点就是内存越界,泄漏,未初始化变量等Bug非常头疼,特别是在游戏逻辑非常复杂之后,加上一些多线程行为,所以一旦出这种bug,是非常难调试的,所以一般会在引擎一开始就引入一套完善的内存统计和调试机制。这样也能精确的控制游戏各个模块的内存使用。对Console来说这个也非常重要。Doom3引入了一套比较复杂的内存调试机制。除了常规的重载内存分配机制。还加入了一套TypeInfo生成机制。下面主要分析一下这套机制。在编译gamex86.dll的过程中,会在game/gamesys下生成一个GameTypeInfo.h文件。这个文件只有在ID_DEBUG_MEMORY宏定义的情况下才会包含进工程。

#ifdef ID_DEBUG_MEMORY
#include "GameTypeInfo.h" // Make sure this is up to date!
#else
#include "NoGameTypeInfo.h"
#endif
//在Debug With Memory的工程设置里面有这个编译条件的定义:
//ID_REDIRECT_NEWDELETE;ID_DEBUG_MEMORY;ID_DEBUG_UNINITIALIZED_MEMORY;%(PreprocessorDefinitions)
//GameTypeInfo.h里面生成的表结构是这样的:
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;static constantInfo_t constantInfo[] = {{ NULL, NULL, NULL }
};static enumTypeInfo_t enumTypeInfo[] = {{ NULL, NULL }
};static classTypeInfo_t classTypeInfo[] = {{ NULL, NULL, 0, NULL }
};

也就是所有的变量的类型信息,名字,相对对象起始地址的内存偏移量。需要注意的是:所有的模板类是不生成类型信息的,因为模板会在编译期生成多种实例代码。Bit Fields也不生成,因为无法获得其地址偏移,不支持多重继承,只建立单一继承的链表关系。所有的变量类型信息都是带词法信息的,比如类里面包含的constant是这样的:

{ “int”, “idMapPrimitive::TYPE_PATCH”, “1″ },第一个是类型,第二个是名字,第三个是常量值

对于类变量则是这样的:

{ “idList < const function_t * >“, “functions”, (int)(&((idTypeDef *)0)->functions), sizeof( ((idTypeDef *)0)->functions ) },

最复杂的是第三项,这个表达式其实是这个变量在类实例中的内存偏移量。这样,在运行时,只要有类实例的指针,加上这个offset,就是这个变量的内存区域起始地址了。

TypInfo信息是这样使用的:

1.在CreateInstance的时候检查是否有构造函数未初始化的变量存在。类实例创建之后,立即调用FindUninitializedMemory函数,这个函数会检查是否有内存特殊标记值的存在,如果发现,则查询TypeInfo信息表,获得类名和变量名。输出警告。

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;
for ( int i = 0; i < size; i++ ) {if ( ptr[i] == 0xcdcdcdcd ) {const char *varName = GetTypeVariableName( GetClassname(), i << 2 );
gameLocal.Warning( "type '%s' has uninitialized variable %s (offset %d)", GetClassname(), varName, i << 2 );
}
}
#endif
}const char *GetTypeVariableName( const char *typeName, int offset ) {static char varName[1024];
int i;for ( i = 0; classTypeInfo[i].typeName != NULL; i++ ) {if ( idStr::Cmp( typeName, classTypeInfo[i].typeName ) == 0 ) {if ( classTypeInfo[i].variables[0].name != NULL && offset >= classTypeInfo[i].variables[0].offset ) {break;
}
typeName = classTypeInfo[i].superType;
if ( *typeName == '\0' ) {return "<unknown>";
}
i = -1;
}
}const classTypeInfo_t &classInfo = classTypeInfo[i];for ( i = 0; classInfo.variables[i].name != NULL; i++ ) {if ( offset <= classInfo.variables[i].offset ) {break;
}
}
if ( i == 0 ) {idStr::snPrintf( varName, sizeof( varName ), "%s::<unknown>", classInfo.typeName );
} else {idStr::snPrintf( varName, sizeof( varName ), "%s::%s", classInfo.typeName, classInfo.variables[i-1].name );
}
return varName;
}

2.在创建时的检查没有递归遍历指针对象,所以可能会有一定的遗漏,所以在idRestoreGame::CreateObjects的时候,会对每一个创建出来的对象,执行idTypeInfoTools::InitTypeVariables。将所有变量初始化为一个特殊值。同时检查指针指向的对象里面是否有没有初始化的变量。

void idTypeInfoTools::InitTypeVariables( const void *typePtr, const char *typeName, int value ) {idTypeInfoTools::fp = NULL;
idTypeInfoTools::initValue = value;
idTypeInfoTools::Write = InitVariable;
WriteClass_r( typePtr, "", typeName, "", "", 0 );
}

递归的查询TypeInfo表,把每一个变量的内存值都填成一个特殊值(比如0xCE)。同时在这个过程中,检查是否有变量还是没有初始化过的值(在分配内存时,会把内存值全部填充成0xCDCDCDCD)。如果有,则报警。

void idTypeInfoTools::WriteClass_r( const void *classPtr, const char *className, const char *classType, const char *scope, const char *prefix, const int pointerDepth ) {int i;const classTypeInfo_t *classInfo = FindClassInfo( classType );
if ( !classInfo ) {return;
}
if ( *classInfo->superType != '\0' ) {WriteClass_r( classPtr, className, classInfo->superType, scope, prefix, pointerDepth );
}for ( i = 0; classInfo->variables[i].name != NULL; i++ ) {const classVariableInfo_t &classVar = classInfo->variables[i];void *varPtr = (void *) (((byte *)classPtr) + classVar.offset);WriteVariable_r( varPtr, classVar.name, classVar.type, classType, prefix, pointerDepth );
}
}
//设置对象变量的初始值
void idTypeInfoTools::InitVariable( const char *varName, const char *varType, const char *scope, const char *prefix, const char *postfix, const char *value, const void *varPtr, int varSize ) {if ( varPtr != NULL && varSize > 0 ) {// NOTE: skip renderer handles
if ( IsRenderHandleVariable( varName, varType, scope, prefix, postfix, value ) ) {return;
}
memset( const_cast<void   *>(varPtr), initValue, varSize );
}
}
//检查是否有变量是否初始化的代码如下,如果没有初始化,就打印出详细的变量信息
do {if ( *((unsigned long *)varPtr) == 0xcdcdcdcd ) {common->Warning( "%s%s::%s%s uses uninitialized memory", prefix, scope, varName, "" );
break;
}
} while( ++i < typeSize );

基本原理就是这样的,遇到指针和数组的时候,情况会更复杂一些,总的来说是提供了一套在运行时动态检查所有变量的机制。在看这个代码的是否我就在想,ID的静态分析工具是怎么用的?后来看到一篇卡马克的博客,发现他们也是静态分析的狂热支持者,不断的寻求更好的静态分析工具,这篇文章非常值得一看。PC-Lint我们也试用过一段时间,但是最后还是没有用起来。其他的几个工具准备好好评估下。毕竟引擎代码库的质量对项目来说非常关键。

No related posts.

DOOM3 源码分析笔记(1)相关推荐

  1. SharpDevelop源码分析笔记(一)

    SharpDevelop自动命令启动UI部分(看SharpDevelop源码分析笔记随想) 参见:Fbt2008的大作  SharpDevelop源码分析笔记(一) 源文档 <http://ww ...

  2. Clamav杀毒软件源码分析笔记 六

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Clam ...

  3. Retrofit源码分析笔记(一)

    如遇图片无法加载请点击此链接 我们先从最简单的Retrofit使用方法看 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n7bbuois-1665971394319)( ...

  4. Clamav杀毒软件源码分析笔记 九

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Clam ...

  5. Clamav杀毒软件源码分析笔记[九]

    Clamav杀毒软件源码分析笔记[九] 刺猬@http://blog.csdn.net/littlehedgehog [数据流病毒扫描] 数据流病毒扫描,听上去貌似很牛逼的称呼,其实就是一个传送数据流 ...

  6. 基于Linux的UART驱动框架源码分析笔记

    文章目录 前言 一.I.MX6ULL串口接收和发送方式 1.非DMA方式 1.1.接收方式 1.2 发送方式 2.DMA方式 2.1.接收方式 2.2 发送方式 二.UART驱动注册 1.uart_r ...

  7. openmp官方源码_MNN推理过程源码分析笔记(一)主流程

    在正式开始推理代码分析之前, 回顾下 MNN整体结构 推理分为三个大部分 Engine Backends Runtime Optimize 那么问题来了,从哪里开始,怎么入手呢? 我的心得是源码分析不 ...

  8. Dubbo源码分析笔记-一(工程目录介绍)

    Dubbo 是阿里开发的分布式服务调用框架,提供了它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. 工程目录 模块介绍 dubbo-common   Dubb ...

  9. vue实现消息badge 标记_Badge组件_element-ui源码分析笔记 - SegmentFault 思否

    Badge组件主要用于数字或状态的标记,对于消息类的提醒功能,使用这组件还是很常见的.具体显示效果如下图: 不管组件复杂还是简单,编码实现这个组件的都不是源码分析目的. 源码分析,在于通过一步步的实现 ...

  10. linux内核源码分析笔记

    一.内核源码目录结构 1.Linux 内核源代码包括三个主要部分 1)内核核心代码:包括linux内核整体架构分析笔记描述的各子系统和子模块,以及其他支撑子系统,如:电源管理.linux初始化等. 2 ...

最新文章

  1. 海外名校毕业!好不容易凭借超强的面试能力+算法入职的谷歌新员工被批干活太慢,委屈得要哭!作为职场新人该怎么办?...
  2. visual studio code(vs code)如何更换颜色背景
  3. cat/tac/more/less 命令详解
  4. (转载)Android进阶2之Activity之间数据交流(onActivityResult的用法)
  5. OpenCV添加(混合)两个图像
  6. 手机端公告文本回滚(简单的jq代码)
  7. asp.net中的异步页面
  8. Java 内存溢出(java.lang.OutOfMemoryError)解决
  9. mysql 使用service mysqld start 提示未识别服务 进入/etc/rc.d/init.d 下面未发现有mysqld解决方法
  10. pe怎么删除linux文件夹,PE环境下,面对无法删除的文件夹怎么办?
  11. Silverlight查询大数据出错?使用服务器端分页控件DomainDataSource
  12. Maven进行Mahout编程,使其兼容Hadoop2.2.0环境运行 (转)
  13. 基于Cookie跨域的单点登录问题
  14. 装饰器结构应用与基本使用(611)
  15. 软件测试基础知识思维导图,测试基础思维导图.pdf
  16. 微信小程序的所有scene场景值 2020-10-21
  17. 双堆1.数据流的中位数
  18. 邮件服务器专用术语,邮件群发中的常见术语
  19. 单词数 HDU - 2072
  20. 视频工厂:如何拍摄优质的餐饮宣传片之品牌植入篇

热门文章

  1. (转载)李剑英的CSLight入门指南结合NGUI热更新
  2. 微信小程序监听手势左右滑动
  3. office2007加载缓慢_word2007打开特别慢怎么解决
  4. 如何理解「朝闻道,夕死可矣」?
  5. web应用基本框架图
  6. Java8 stream新定义运算
  7. Redux中的reducer到底是什么,以及它为什么叫reducer?
  8. hadoop reducer不执行问题及解决
  9. TX2081ALBJ3超低功耗半导体指纹识别模组 智能锁 指纹考勤打卡 指纹门禁系统应用
  10. 阿里测开岗定级P7全流程加面试真题