今天在调试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字节指针指向代码段的虚函数表。
然后,强制类型转换在执行中的表现会受到头文件的影响:
-
我导入了
Shape
的完整定义头文件,那么对Shape*
和Object*
进行强制类型转换会自动进行这8个字节的加减。 -
我没有导入
Shape
的完整定义头文件,但是导入了其前置声明,则编译器什么都不知道,它会把Shape*
和Object*
之间的强制类型转换会不进行任何算数运算,直接完成。
而恰好,我对Shape*
指针的传递和变换发生在多个源文件内,其中有一些没有导入Shape
完整定义,一些导入了Shape
完整定义,这让这两种转换的表现不一,导致最后的指针和正确的指针偏了8个字节。
这个错误最鬼畜的地方就在于不会报任何编译警告,而且对C++11不太了解的人调试起来会完全没有头绪。还好Hineven学过ics,不然可能就挂在这儿了。
在合适的地方导入包含Shape
全套声明的头文件,bug解决。
之后
哇!CHEN又成功跑起来了!来康康新功能效果怎么样~
【吐血】