一个有趣的错误

内容纲要

今天在调试CHEN的新版本时,加入了一点新功能,修改了某个文件的头文件引入,然后,程序莫名奇妙的段错误了。经过检查没有发现野指针,用gdb看看发生了什么。

Thread 3 "chen" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff5029700 (LWP 9680)]
0x00005555555607b2 in chen::SurfInteract::applyMaterial (this=0x7ffff50277c0, dir=false) at src/core/interaction.cpp:17
17          return ((const Shape*)hit)->material->apply(this, dir);

gdb表示事情不妙,在interaction.cpp:17的短函数内发生了段错误。
只有可能是几个指针中有东西出问题了,输出material发现它指向了奇怪的低地址,很不对劲。
于是又检查了一遍代码的其他位置,看看任何存在的Shape实例会不会有material成员未初始化的状况。

结果,没有。

这就很诡异了。

难道是hit出了问题?gdb发现hit指向栈空间里,很合理,而我的程序里,hit只能通过Shape类型实例用this指针进行赋值得到,也没有Shape实例在程序运行中被释放,不可能出问题。不过所幸Shape的总数很少,但是打印hit,发现是(const chen::Object *) 0x7fffffffbe70,把所有可能的Shape地址打印出来之后,发现存在(const chen::Shape *) 0x7fffffffbe70

但,问题来了。

劳子的虚函数表呢?

在项目的代码中,Object类是一个旧式的类,也就是说,它的内存结构只是简单地把数据对齐后堆在一起。而Shape类继承了Object并定义了虚函数。于是Shape类的实例在内存结构上会有一个前置的8字节指针指向代码段的虚函数表。

然后,强制类型转换在执行中的表现会受到头文件的影响:

  1. 我导入了Shape的完整定义头文件,那么对Shape*Object*进行强制类型转换会自动进行这8个字节的加减。

  2. 我没有导入Shape的完整定义头文件,但是导入了其前置声明,则编译器什么都不知道,它会把Shape*Object*之间的强制类型转换会不进行任何算数运算,直接完成。

而恰好,我对Shape*指针的传递和变换发生在多个源文件内,其中有一些没有导入Shape完整定义,一些导入了Shape完整定义,这让这两种转换的表现不一,导致最后的指针和正确的指针偏了8个字节。

这个错误最鬼畜的地方就在于不会报任何编译警告,而且对C++11不太了解的人调试起来会完全没有头绪。还好Hineven学过ics,不然可能就挂在这儿了。

在合适的地方导入包含Shape全套声明的头文件,bug解决。

之后

哇!CHEN又成功跑起来了!来康康新功能效果怎么样~

【吐血】

此条目发表在文章分类目录,贴了标签。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注