项目工程中,准备对一个基础的类进行封装,封装成共享库(动态/静态)是不错的选择。
之前在Windows都是照猫画虎的搞一下,没有什么深刻的印象。但是这次在Linux下的类的导出真是刻骨铭心:各种灵异现象,各种悲剧,各种内存出错!
特来分享一下,与大家共勉~ 如果直接想看结论,调到文章最后好了,就一句话。
好吧,一个非常灵异,但其实是很二的故事,黑贝狗~
一个非常简单的类:
class CTest
{
public:
CTest( int a );
~CTest();
private:
int a;
int b;
int c;
int d;
};
CTest :: CTest( int a )
{
a = 0; b = 0; c = 0; d = 0;
printf( "[%s]********************\n", __func__ );
printf( "a Addr: %p\n", &a );
printf( "b Addr: %p\n", &b );
printf( "c Addr: %p\n", &c );
printf( "d Addr: %p\n", &d );
}
真的是什么也没做,然后在不明真相的情况下使用这个类,神奇的事情就发生了:
1. 如果new了CTest,
printf( "call new CTest" );
CTest *pTest = new CTest( 1 );
assert( NULL != pTest );
if ( NULL != pTest ) {
delete pTest;
}
那么在delete的时候就会内存出错,各种严厉、奇怪的告警。
call new CTest[CTest]********************
a Addr: 0xbfad1dc4
b Addr: 0x902000c
c Addr: 0x9020010
d Addr: 0x9020014
[~CTest]********************
*** glibc detected *** ./main: free(): invalid next size (fast): 0x09020008 ***
======= Backtrace: =========
/lib/libc.so.6[0x4fb394]
/lib/libc.so.6(cfree+0x96)[0x4fd346]
/usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x7182591]
./main[0x8048c26]
./main[0x8048d26]
/lib/libc.so.6(__libc_start_main+0xe5)[0x4a26d5]
./main[0x80488b1]
其实可以看到一些端倪:此处CTest new出来的对象的成员地址是非常奇怪的。
2. 那么如果直接声明一个对象呢?
貌似没什么问题,但其实是有问题的,而且要命!
测试代码:
void func1( void )
{
char array[ARRARY_SIZE];
memset( array, 0x5A, ARRARY_SIZE );
for (int i = 0; i < ARRARY_SIZE; i++) {
printf( "array[%02d]: %02d, Addr: %p\n", i, array[i], &(array[i]) );
}
CTest test( 1 );
#if 0
printf( "call new CTest" );
CTest *pTest = new CTest( 1 );
assert( NULL != pTest );
if ( NULL != pTest ) {
delete pTest;
}
#endif
for (int i = 0; i < ARRARY_SIZE; i++) {
printf( "array[%02d]: %02d, Addr: %p\n", i, array[i], &(array[i]) );
}
}
测试结果:
array[00]: 90, Addr: 0xbf8fabf4
array[01]: 90, Addr: 0xbf8fabf5
array[02]: 90, Addr: 0xbf8fabf6
array[03]: 90, Addr: 0xbf8fabf7
array[04]: 90, Addr: 0xbf8fabf8
array[05]: 90, Addr: 0xbf8fabf9
array[06]: 90, Addr: 0xbf8fabfa
array[07]: 90, Addr: 0xbf8fabfb
array[08]: 90, Addr: 0xbf8fabfc
array[09]: 90, Addr: 0xbf8fabfd
array[10]: 90, Addr: 0xbf8fabfe
array[11]: 90, Addr: 0xbf8fabff
array[12]: 90, Addr: 0xbf8fac00
array[13]: 90, Addr: 0xbf8fac01
array[14]: 90, Addr: 0xbf8fac02
array[15]: 90, Addr: 0xbf8fac03
array[16]: 90, Addr: 0xbf8fac04
array[17]: 90, Addr: 0xbf8fac05
array[18]: 90, Addr: 0xbf8fac06
array[19]: 90, Addr: 0xbf8fac07
[CTest]********************
a Addr: 0xbf8fabd4
b Addr: 0xbf8fabf7
c Addr: 0xbf8fabfb
d Addr: 0xbf8fabff
array[00]: 90, Addr: 0xbf8fabf4
array[01]: 90, Addr: 0xbf8fabf5
array[02]: 90, Addr: 0xbf8fabf6
array[03]: 00, Addr: 0xbf8fabf7
array[04]: 00, Addr: 0xbf8fabf8
array[05]: 00, Addr: 0xbf8fabf9
array[06]: 00, Addr: 0xbf8fabfa
array[07]: 00, Addr: 0xbf8fabfb
array[08]: 00, Addr: 0xbf8fabfc
array[09]: 00, Addr: 0xbf8fabfd
array[10]: 00, Addr: 0xbf8fabfe
array[11]: 00, Addr: 0xbf8fabff
array[12]: 00, Addr: 0xbf8fac00
array[13]: 00, Addr: 0xbf8fac01
array[14]: 00, Addr: 0xbf8fac02
array[15]: 90, Addr: 0xbf8fac03
array[16]: 90, Addr: 0xbf8fac04
array[17]: 90, Addr: 0xbf8fac05
array[18]: 90, Addr: 0xbf8fac06
array[19]: 90, Addr: 0xbf8fac07
触目惊心的内存访问越界!但是我自认为什么也没干啊,只不过是声明了一个CTest对象而已啊!
意识到问题的严重之后,我重新把类加回到主程序之中,一切又恢复原样了……
把凌乱的情况总结一下:
1. 我定义了一个类,使用共享库的方式导出;
2. 使用这个库,无论是直接声明对象,还是new一个,都是出现了内存的错误!
3. 拿回到主程序,一切正常。
这个情况让我一度怀疑gcc啊,类的导出机制啊这些我根本就不懂的东西,觉得人生如此凶险。
好吧,谜题揭晓吧,是一个非常二的地方——我在库的导出时操作都是OK的,但是使用库的时候,我自作聪明地“为了隐藏尽可能多的细节”,供外部使用的头文件中去除了所有的私有成员!
是的,这是我犯二时候使用的库的导出头文件,没有错,就是案发现场:
class CTest
{
public:
CTest( int a );
~CTest();
};
当意识到问题出在这里之后,答案貌似就很简单了——使用库的时候,根本不知道你这个对象需要分配多大的内存空间!
使用new的时候,首个成员在栈上,其余在堆上;直接声明时,首个成员OK,但是其余成员则是被安排到了其他的地址段了,覆盖了其他的变量。
说了这么多,其实都是我在异常情况下的困顿经历。
血的教训就一句话:类在供外部使用时,其成员不应该隐藏,否则会直接影响内存空间的分配。