新入手的YAMAHA MCR-040迷你音响,和大黄蜂一个色的,bloody gorgeous!!!
从此,我收藏的那么多CD终于有了着落,因为它本身就是个CD播放器,并且它还是个iPod音响,同时,家里的DVD和电视机也能接上播放,相当于mini家庭影院了。
新入手的YAMAHA MCR-040迷你音响,和大黄蜂一个色的,bloody gorgeous!!!
从此,我收藏的那么多CD终于有了着落,因为它本身就是个CD播放器,并且它还是个iPod音响,同时,家里的DVD和电视机也能接上播放,相当于mini家庭影院了。
用cmake生成OGRE的项目文件,却出不来Sample项目。cmake的输出信息中提示Skipping samples build,因为Dependecies中的OIS没有编译成功,有以下错误:
dxguid.lib(dxguid.obj) : fatal error LNK1103: debugging information corrupt; recompile module
google了一下,发现很多都是说因为某个版本的DX不再支持VC6了,但我用的是VS 2005,不过DX SDK版本是最新刚刚下载的,猜想出现这个错误是不是也是因为DX和VS版本兼容的问题。
果然,我的DX版本是June 2010,官方网站上申明这个版本开始支持VS 2010,但不再支持VS 2005了。
The June 2010 DirectX SDK includes support for Visual Studio 2010. The DirectX SDK will continue to support Visual Studio 2008 as well. However, Visual Studio 2005 will no longer be supported.
解决办法:
国庆前就在淘宝上痛下血本拍下了《Game Engine Architecture》,本来想在国庆长假内好好消化这本书,但快递公司居然也休了7天的长假,自从10月1日临晨后,物流信息就停止了更新。结果就是,回老家过了一个漫长而慵懒的十一假期。
从去年就开始关注这本书,据说好评如潮。我最近一年多一直干的事情就是开发game engine,但我对目前开发的很多架构不太满意,也有很多疑惑,虽然觉得有些地方很不完善,却又没法找到好的方法解决,所以希望能得到一些比较系统比较权威的帮助以作参考。于是买下了这本书,据说在国外是作为本科生的教材,所以讲得比较基础,不过比较全面、系统。
书很厚,800多页,每晚回去看一些,争取两个月内能看完。
这本书在google book上有预览,不过不是很清楚。
Recently I’ve been starting to use Ubuntu. I am just a newbie to Linux, so everything seems hard for me on the first time. No matter how familiar we are with Windows, when transferred to the Linux world, we become a computer idiot again. But everything is also fresh and attractive for me. When I get my first Ubuntu 10.04 (the latest version at this time) work on my laptop, I was totally shocked, really bloody cool~ (I learned the word “bloody” from a British TV show called IT Crowd, which I am watching recently)
Dropbox is a perfect software to sync your files among different computers and I use it quite often under Windows. And now under Linux, I want it, too. But here in China, Dropbox is violently blocked. So in order to use it, I need to get out of the firewall first.
Get an SSH tunnel
It’s a common way to use SSH to get over the wall. Getting an SSH tunnel is easy, there are some for free. And you could also buy one for the sake of stability and speed. I bought a Puff (which is a kind of software using SSH) account from China’s biggest C2C shopping site Taobao.com.
Use gSTM as the SSH tunnel manager under Linux
Get gSTM from here: http://sourceforge.net/projects/gstm/
Configure gSTM:
Start the tunnel you’ve just added. If you set your browser’s proxy as SOCK5, 127.0.0.1:7070, you should be able to surf the sites like Facebook and Twitter now. If so, certainly you can log on to www.dropbox.com too!
Download Dropbox for Linux
Go to https://www.dropbox.com/downloading, and download a Dropbox for linux. I’m using Ubuntu, so I downloaded the Ubuntu (x86 .deb). Double click the downloaded .deb file, it will start the Ubuntu’s package installer to install Dropbox.
Download Dropbox daemon
After installation and starting Dropbox, it will prompt you to download the Dropbox daemon, but the proxy we set up before only works for the browser, so I cannot get the downloading work. The problem here is how could I let an arbitrary software to access the Internet with proxy. The solution is to use ProxyChains.
Use ProxyChains
Install ProxyChains: sudo apt-get install ProxyChains
Configuration
Now you should be able to use the SSH proxy to download Dropbox daemon.
Open terminal, and enter: proxychains dropbox start -i
This will start Dropbox with the SSH proxy. After the daemon is downloaded and installed, we could enjoy Dropbox again.
最近写的模块,在独立的应用程序中测试是没问题的,但把它装配成DLL后,再在另一个应用程序中调用时却出现了内存错误。程序的模块链接关系大概是这样的:
module就是我所写的模块,在这里被封装为DLL,因为要使用json相关的功能,该DLL链接了一个静态库 (jsoncpp.lib)。最后在应用程序中导入并使用module.dll,同时因为在应用程序中也需要用到json,所以应用程序也链接了jsoncpp.lib。
以下用一些伪代码来描述这些模块间的调用关系,以具现出这个错误。
jsoncpp.lib为c++提供了功能齐全的json操作,其核心的类是Json::Value。(阅读本篇文章你无需了解太多json)
module.dll中导出了一个接口:
//ModuleClass.h #include "json/value.h" #if defined MODULE_EXPORTS #define MODULE_EXPORTS __declspec(dllexport) #else #define MODULE_EXPORTS __declspec(dllimport) #endif class ModuleClass { public: MODULE_EXPORTS void AllocSomeMemory( Json::Value &root ) { // 这将申请一些内存,因为会new出一个Json::Value,并append到root上 root.append( "testString" ); } };
应用程序:
#include "json/value.h" #include "ModuleClass.h" int main() { Json::Value root; ModuleClass::AllocSomeMemory( root ); }
在Debug模式下,当main函数执行完毕,对Json::Value root进行析构时,程序便出现了异常。分析下,很显然,调用ModuleClass::MallocMemoryHere时申请的内存,是在module.dll中申请的,而对这些内存的析构则是在应用程序(.exe)中进行的(析构root会同时析构append在root上的所有子Json::Value)。不过,这是异常的真正原因么?
追踪到异常的出错点:dbgheap.c文件中那句ASSERT语句。
/* * If this ASSERT fails, a bad pointer has been passed in. It may be * totally bogus, or it may have been allocated from another heap. * The pointer MUST come from the 'local' heap. */ _ASSERTE(_CrtIsValidHeapPointer(pUserData));
注释中的最后一句话”The pointer MUST come from the ‘local’ heap“引起了我的警惕,难道对于内存的申请和释放不是在同一个heap上,除了‘local’ heap还有一个什么heap么。
去MSDN上搜索了关于_CrtIsValidHeapPointer,似乎找到了答案,以下这段话是MSDN上对于_CrtIsValidHeapPointer的介绍:
The _CrtIsValidHeapPointer function is used to ensure that a specific memory address is within the local heap. The local heap refers to the heap created and managed by a particular instance of the C run-time library. If a dynamic-link library (DLL) contains a static link to the run-time library, it has its own instance of the run-time heap, and therefore its own heap, independent of the application’s local heap. When _DEBUG is not defined, calls to _CrtIsValidHeapPointer are removed during preprocessing.
注意字体加粗的部分,这不正应对我的情形么?!错误不在于DLL中申请的内存在EXE中释放,而在于如果这个DLL拥有一个静态链接,它就会拥有独立的运行时堆,独立于应用程序的堆。这样对于内存申请和释放并不是在同一个堆上进行的,当然出错了。
解决:虽然MSDN上最后说,如果把项目改成release的,这个ASSERT就将避免,但这是放纵内存泄露,最好的解决办法是将静态链接也改成动态链接,这样就使得DLL能够和应用程序共享同一个堆,错误也得以避免。
于是,我修改了jsoncpp的项目配置,生成jsoncpp的动态链接库,而不是使用静态库,重新导入到module.dll中,错误解决。
Static Initialization Order Fiasco (SIOF),我也是最近才知道了这个说法,因为在开发程序的时候被它bug了:对于一个static变量,不管它是全局的或者是类的成员变量,访问它的时候不一定总是成功的,甚至会造成程序crash,因为不能保证它在被访问时已经被初始化了(跟初始化的顺序有关,所以称为初始化顺序的Fiasco)。以下将制造一个非常简单的SIOF情形:
Whatever.h
#include <vector> #include <string> class Whatever { public: Whatever() { cout << "Construct Whatever" << endl; Display(); } ~Whatever() { cout << "Destruct Whatever" << endl; Display(); } void Display() { cout << "static int:" << i << endl; cout << "static string:" << m_str << endl; cout << "static vector:" << m_vec.front() << endl; } private: static int i; static std::string m_str; static std::vector<char> m_vec; };
Whatever.cpp
#include "Whatever.h" int Whatever::i = 500; string Whatever::m_str = "something"; vector<char> Whatever::m_vec = vector<char>( 10, 'a' );
一个简单的类,Whatever,包含几个static成员变量,然后在构造函数和析构函数中都分别打印这些静态变量的值,乍一看似乎没什么问题,但却有潜在的SIOF的风险。我们容易默认为在调用Whatever的构造函数的时候,Whatever空间中的static的成员变量已经被初始化了,其实不然,现在制造一个SIOF引起crash的情形:
#include "Whatever.h" Whatever g_whatever; int main() { ... }
因为g_whatever是global变量,所以最先被初始化,在调用Whatever的构造函数的时候,Whatever空间的静态成员变量还未被初始化,所以访问这些静态变量肯定出错。在VS的编译器下测试的结果:
Construct Whatever
static int:5
static string:
(调用m_vec.front()导致程序crash)
奇怪的是对于int这种built-in的类型却能得到正确的值,不知编译器在背后都做了哪些手脚,猜想可能是在程序编译的时候他们就被值替换了。而string和vector应该都属于自定义类型(初始化需要调用构造函数),未初始化之前访问肯定是错误的,所以打印出的string是个空值,而访问一个空的vector的front元素则直接造成程序crash。
根据初始化和释放的对称关系,所以在析构函数中访问这些静态变量同样也是失败的,因为在析构g_whatever的时候,Whatever空间的静态变量已经被解决掉了。
SIOF是非常难于检测的问题,这个例子是一种最简单的情形,在我的项目中,我并没有定义什么global的成员,但是因为使用了很多前置声明(forward declaration),还有一些Singleton,造成了一个非常隐蔽的SIOF,花了很大的力气才找到,痛苦的过程。
要解决SIOF问题,需要用一个function来包装static变量,即利用函数内static变量的construct-on-first-use特性。
修改后的Whatever.h
class Whatever { public: Whatever() { cout << "Construct Whatever" << endl; Display(); } ~Whatever() { cout << "Destruct Whatever" << endl; Display(); } void Display() { cout << "static vector:" << GetStaticVector().front() << endl; } private: vector<char>& GetStaticVector() { static vector<char> vec = vector<char>( 10, 'a' ); return vec; } };
用GetStaticVector来包装之前所需要的静态的vector,就能保证在调用的时候,它一定已经被初始化了。再次运行之前的测试程序,OK了。
总之,我们对于static变量的使用要保持一颗警惕的心,如果不确定在使用时它是否已经被初始化,就要使用函数包装static变量来防止Static Initialization Order FIASCO!
Fiasco, what a cool word.
最近因为在公司的项目中接手了Lua脚本模块的开发,所以研究了很多关于Lua脚本的东西,秉着“多看多想多记”的原则,我时刻敦促自己要及时记录下遇到的一些问题和想法。
在Lua中,几乎所有的东西都是围绕着lua_State转,所以,一般我们都会写一个类来封装管理它,比如:
class LuaObject { public: LuaObject() { m_L = luaL_newstate(); luaL_openlibs( m_L ); } ~LuaObject() { if ( m_L ) { lua_close( m_L ); m_L = NULL; } } private: lua_State *m_L; };
这很好,不过它不是指针安全的。试想,如果一个LuaObject对象被复制,结果将会怎样。
LuaObject luaObject1; LuaObject luaObject2( luaObject1 );
上面这段代码将会导致运行时crash,因为luaObject1和luaObject事实上指向了同一块lua_State,这样当luaObject1和luaObject2被析构时,lua_State会被两次lua_close,这不crash才怪呢!
事实是,当一个类包含了一个指针时,我们就需要开始变得格外谨慎,除了在构造函数和析构函数中要处理指针的初始化和清理外,我们还需要考虑深拷贝(deep copy),浅拷贝(shallow copy))的问题。如果使用编译器默认生成的拷贝构造函数,它只会浅拷贝指针,而指针所指向的内存区域不会被拷贝。就像上面一样,两个LuaObject实则共享了一个lua_State。
那该如何处理让管理LuaObject类的指针安全呢,深拷贝?厄,首先我也没有深究深度拷贝lua_State具体该如何完成,不过我猜想这可是一个复杂而重型的操作,仅仅为了带来指针安全而选择此可不是一个明智的选择。
如果你是开发LuaObject类的程序员,也许你会对使用LuaObject的程序员(或许有时使用者就是你自己)说:“你不要对它进行拷贝操作不就OK了”,但这是一种严重不负责任的行为,因为这种皮球会越踢越远的。比如,程序员A使用了你的LuaObject类,他写了一个包含LuaObject的指针的类,同样A也不考虑拷贝指针安全问题,然后A又将它的类传递给了B,B封装了A的类指针,然后传递给了C,接着如此重复,C再给D,D再给E,最后E在对类进行拷贝操作时,程序crash掉了,因为在某个最底层,他们共享了lua_State。不过因为这时候已经嵌套了这么多层,E程序员或许根本不知道也不关心什么是LuaObject(他只跟D的类打交道)。这时,要想追踪到BUG之源——万恶的不考虑指针安全的LuaObject,已经非常困难了,God bless you all.
事实是,如果你不想让你的类被复制,你就应该明确而显示的禁止它。其实lua_State的这个问题非常类似于Effective C++书中的第14个条款中所提到的问题:在资源管理类中小心copying行为。对于这个问题,书中提供了两种解决方案。第一种就是禁止复制。第二种则是对底层资源的“引用计数法”(reference count)。
第一种禁止复制很简单。(关于Uncopyable)
class LuaObject : private Uncopyable { public: ... }
第二种则是使用智能指针来封装lua_State,我们可以使用boost的shared_ptr来封装lua_State,即shared_ptr< lua_State > m_L。如何对m_L进行初始化是另一个需要注意的问题,如果使用shared_ptr的常见初始化形式:m_L = shared_ptr< lua_State >( luaL_newstate() ),这样是不对的,因为在这种形式下,当lua_State的计数变为0时,shared_ptr会去调用lua_State的的析构函数,这显然是错误的,对lua_State的释放动作是lua_close而不是删除。事实上,这样编译器也无法通过,如果这么写,会报出“use of undefined type ‘lua_State’”的错误,提示lua_State是一个非完成的类型(incomplete type)。
我们应该为shared_ptr的初始化传入一个删除器(deleter)。很显然,lua_State的deleter是lua_close()函数,这样最终的代码如下。
#include <boost/shared_ptr.hpp> using boost::shared_ptr; class LuaObject { public: LuaObject() { m_L = shared_ptr< lua_State >( luaL_newstate(), lua_close ); luaL_openlibs( m_L.get() ); } private: shared_ptr< lua_State > m_L; };
这样再回到前面拷贝的那个例子,luaObject1和luaObject2共同引用了一个lua_State,但因为使用了shared_ptr,所以只有在lua_State的引用次数变为0时,它的deleter(这里是lua_close)才会被调用,安全了!
在我的项目中,因为没有使用boost库,也没有提供任何智能指针,所以使用禁止复制来保证安全。
今天去了工体,因为晚上有巴萨的训练。本来有机会可以让媒体带进去的,但是在最后时刻还是把机会让给了好友。只能感到非常的遗憾了,很多年才有这么一次机会,那些热爱的球星与自己只有一墙之隔,却只能在场外感叹唏嘘。巴萨来到北京已经好几天了,而我最近的状态几乎是被工作所淹没了,作为一个绝对的巴萨死忠,这次中国行似乎跟自己没有半毛钱关系,我真的是老了么…
当巴萨的大巴到达体育场外时,我奔跑着追逐到了梅西的脸庞,爬在工体的铁门上,我远远的望到了伊布的身影,这就是我今天所有追逐的收获。那一刻,觉得他们距离自己是那么的接近,却突然又感觉如此的遥远。但,作为亿万巴萨球迷之一,这已是一种莫大的幸福。
明天,鸟巢见。
Visca Barca
Recent Comments