静态链接导致的一个bug分析
环境介绍和问题重现
在Linux环境下,有两个静态链接库,分别为staticA和staticB,一个应用程序为app。
其中,静态库A中包含两个类:TestA和Object,如下所示:
1 /********* object.h *******/ 2 class Object 3 { 4 public: 5 Object(); 6 ~Object(); 7 void setValue(int val); 8 int getValue() const; 9 private: 10 int m_value; 11 }; 12 13 /********* object.cpp *******/ 14 #include "object.h" 15 16 Object::Object() 17 { 18 } 19 20 Object::~Object() 21 { 22 } 23 24 void Object::setValue(int val) 25 { 26 m_value = val; 27 } 28 29 int Object::getValue() const 30 { 31 return m_value; 32 } 33 34 /********* testa.h *******/ 35 class TestA 36 { 37 public: 38 TestA(); 39 }; 40 41 /********* testa.cpp *******/ 42 #include "testa.h" 43 #include "object.h" 44 #include <iostream> 45 46 TestA::TestA() 47 { 48 Object o; 49 o.setValue(9999); 50 std::cout << o.getValue() << std::endl; 51 }
静态库B中包含两个类:TestB和Object,对,你没有看错,两个静态库中都有Object类,但是实现不同,这里先留个伏笔。
1 /********* object.h *******/ 2 #include <string> 3 class Object 4 { 5 public: 6 Object(); 7 ~Object(); 8 void setName(const std::string& n); 9 std::string getName() const; 10 private: 11 std::string m_name; 12 }; 13 14 /********* object.cpp *******/ 15 #include "object.h" 16 17 Object::Object() 18 { 19 } 20 21 Object::~Object() 22 { 23 } 24 25 void Object::setName(const std::string& n) 26 { 27 m_name = n; 28 } 29 30 std::string Object::getName() const 31 { 32 return m_name; 33 } 34 35 /********* testb.h *******/ 36 class TestB 37 { 38 public: 39 TestB(); 40 }; 41 42 /********* testb.cpp *******/ 43 #include "testb.h" 44 #include "object.h" 45 #include <iostream> 46 47 TestB::TestB() 48 { 49 Object o; 50 o.setName("testb"); 51 std::cout << o.getName() << std::endl; 52 }
在app项目中只有一个main.cpp,这个app中会使用TestA和TestB,但是不会使用任何Object。
1 #include "testa.h" 2 #include "testb.h" 3 4 int main(int argc, char* argv[]) 5 { 6 TestA ta; 7 TestB tb; 8 return 0; 9 }
编译该项目,打开debug选项,执行后会崩溃,但不是每次都崩溃,出现崩溃的位置在TestB中的o.setName("testb"),提示为stl的字符串赋值错误。
bug分析
该项目中两个静态库之间无引用关系,虽然都有Object类,但实现不同,且由于未相互引用,编译过程和链接过程始终未报重定义错误。实际项目中并没有前述那么清楚。实际的情况是程序总崩溃,而该项目远比上述复杂,实际项目中模块很多,并且开发者也不止一位,遇到程序崩溃问题的开发者只知道两个静态库中的一个staticB,对另一个staticA是完全不知情的,开发者可以看到的现象是调用setName一直崩溃。
下面来分析问题,虽然代码定位到了B中的setName,调试器提示string无法赋值,在两个测试类A和B中加入输出信息,都可以正常输出,说明库编译没有问题。排除了库的编译问题之后,分析可能是执行中函数定位错误导致的。因此可以试验一下两个库中的Object对象是否都有实际对象,分别在两个Object的构造函数中增加输出信息,经试验后发现,A中对象始终无法构造出来。
找到这些后给我们解决问题提供了一个很好的思路,那么在应用中直接构造A中的Object对象,看看是否能够创建该对象。
经试验发现,在app中创建A的Object对象时会报重定义错误,到此终于查明了问题,验证了之前的判断,就是由于符号重定义导致的函数跳转错误,然而比较隐蔽。
解决方案
解决方案比较简单,可以修改为不同的类名,也可以通过加命名空间解决。
问题总结
这个问题解决还比较顺利,同时也提醒了开发者在开发过程中,尤其是C++项目开发过程中命名空间的重要性,注意开发规范。