最近用C++比较多,做一个project的时候遇到一个非常奇怪的问题。在学校的机器上用的是gcc 4.6.3,其他版本的我不知道有没有这个问题,clang上测的也没有这个问题。
简单例子,我有一个库文件foo.cpp
#include <cstdio> void f() { printf("f in foo "); } void g() { printf("g in foo "); f(); }
然后有一个使用这个库的文件test.cpp
void g(); int main() { g(); return 0; }
使用如下的编译命令:
g++ -c foo.cpp -o foo.o g++ -c test.cpp -o test.o g++ test.o foo.o -o test
然后运行./test得到结果
g in foo
f in foo
很正常,符合预期。对于库文件来说,一般是编译成静态或者动态库,比如把foo.cpp编译成libfoo.so,同时提供一个头文件,把其他代码会用到的g()函数列出来,使用时直接include头文件。对应的,几个文件相应改为:
// foo.h #ifndef _FOO_H_ #define _FOO_H_ void g(); #endif // foo.cpp #include <cstdio> void f() { printf("f in foo "); } void g() { printf("g in foo "); f(); } // test.cpp #include "foo.h" int main() { g(); return 0; }
编译时使用:
g++ -fPIC -shared foo.cpp -o libfoo.so g++ -c test.cpp -o test.o g++ -L. test.o -lfoo -o test
一般而言,foo.cpp的实现代码外界是不需要知道很多时候也没法知道的,头文件foo.h唯一定义了这个库所实现的东西。带着这个假设,我就遇到了这么一个问题:由于不知道libfoo.so的实现细节,我自己在test.cpp里也定义了一个函数f():
// test.pp #include "foo.h" #include <cstdio> void f() { printf("f in test "); } int main() { f(); g(); return 0; }
由于我只看得到foo.h,所以我并不知道libfoo.so的实现中也会用到一个叫f()的函数。上面这个代码预期的输出应该是
f in test
g in foo
f in foo
结果输出是
f in test
g in foo
f in test
最后一行的"f in test"就很不可理解了,这表示使用foo.cpp里面的g()函数调用f()的时候实际上是用的test.cpp里面的f()函数。也就是说,调用库的文件中定义的函数把库自己定义的函数给覆盖掉了。这意味着什么呢?意味着很有可能你在使用某个库的时候你运气一不好使用了一个库里面定义好了的名字就可以轻轻松松覆盖掉它,然后整个project完全乱套你还不知道问题出在哪儿。下面这是一个更奇怪的例子,foo.h还是同原来一样定义,foo.cpp和test.cpp定义如下,引入了一个叫做A的类:
// foo.cpp #include <cstdio> class A { public: A() { printf("A() in foo "); } ~A() { printf("~A() in foo "); } }; void g() { printf("g in foo "); A *a = new A(); delete a; } // test.cpp #include <cstdio> #include "foo.h" class A { public: A() { printf("A() in test "); }
~A() { printf("~A() in test "); } }; int main() { A *a = new A(); delete a; g(); return 0; }
这个程序的运行结果是
A() in test
~A() in test
g in foo
A() in test
~A() in test
和前面的猜测一致,这里test.cpp定义的A把库里面定义的A给覆盖掉了,但是,如果把test.cpp里面定义的A的默认构造函数去掉,改为如下
#include <cstdio> #include "foo.h" class A { public: A(int x) { printf("A(%d) in test ", x); } ~A() { printf("~A() in test "); } }; int main() { A *a = new A(10); delete a; g(); return 0; }
则输出结果变为:
A(10) in test
~A() in test
g in foo
A() in foo
~A() in test
这说明g()里面构造的A使用的是foo.cpp里面定义的构造函数,delete a使用的是test.cpp里面的~A()。
实际project中,我遇到的就是这个问题。所有的计算结果都是正确的,结果析构函数里面出了segmentation fault。过了好长时间才找到问题,原来库里面有一个类和我自己定义的类同名。构造函数和其他成员函数都没有出问题,因为没有用到默认构造函数,其他函数形式也都不一样,但析构函数只有唯一的定义形式,所以很容易就出了bug。
学校的gcc 4.6.3有这个问题,但更新版本的gcc不知道有没有同样的问题。我自己电脑上用clang测的就没有这个问题。
如何解决呢?暂时能想到的只有写库的时候多用namespace,或者把库外面看不到的函数和类名字起得怪一点,加点下划线之类的。