今天在写一个函数,需要将map作为一个引用参数传入函数体内部进行赋值,结果编译通过,执行时总是崩溃,在网上找到了一些作者写的blog,详细解释了这种情况发生的原因,特转载在这里,便于自己今后查询。
原文1:有一个功能模块, 本来是写在主程序当中. 现在觉得有必要将它写成一个 DLL. 于是开始代码的移植. 费了好大的劲. 终于移植完成, 通过编译了. 这时运行程序, CRASH!
调试之, 发现是在一个 map 的赋值出现了问题.
看 vc6 自带的 STL 的代码:
map 的赋值操作, 也就是其中的树赋值操作.
_Myt& operator=(const _Myt& _X)
{
_Tr = _X._Tr;
return (*this);
}
树的赋值操作:
_Myt& operator=(const _Myt& _X)
{
if (this != &_X)
{
erase(begin(), end());
key_compare = _X.key_compare;
_Copy(_X);
}
return (*this);
}
先删除自己, 然后调用 _Copy(const _Myt&);
void _Copy(const _Myt& _X)
{
_Root() = _Copy(_X._Root(), _Head);
_Size = _X.size();
if (_Root() != _Nil)
{
_Lmost() = _Min(_Root());
_Rmost() = _Max(_Root());
}
else
_Lmost() = _Head, _Rmost() = _Head;
}
其中又调用了 _Copy(_Nodeptr, _Nodeptr);
_Nodeptr _Copy(_Nodeptr _X, _Nodeptr _P)
{
_Nodeptr _R = _X;
for (; _X != _Nil; _X = _Left(_X)) // error here
{
_Nodeptr _Y = _Buynode(_P, _Color(_X));
if (_R == _X)
_R = _Y;
_Right(_Y) = _Copy(_Right(_X), _Y);
_Consval(&_Value(_Y), _Value(_X));
_Left(_P) = _Y;
_P = _Y;
}
_Left(_P) = _Nil;
return (_R);
}
看标记的那一行. _X 与 _Nil 比较. 其中的 _Nil 如下:
static _Tree<_K, _Ty, _Kfn, _Pr, _A>::_Nodeptr _Tree<_K, _Ty, _Kfn, _Pr, _A>::_Nil = 0;
是一个静态变量. 初始值为 0. 在一个 module (注: 这里的 module 是指的一个exe, 或者 dll. 下同) 中构建第一个 map 实例时, 有这样的代码:
if (_Nil == 0)
{_Nil = _Buynode(0, _Black);
_Left(_Nil) = 0, _Right(_Nil) = 0; }
如果 _Nil 未初始化则创建一个 node, 初始化 _Nil. 然后 map 将内部的 _Head._Parent 指向这个 _Nil.
设想这样一种情形. 一个 EXE, 一个 DLL.
EXE:
void main()
{
map m;
func(m);
}
DLL:
void func(map& m)
{
map n = m;
}
在 EXE 中构建了一个 map 实例. 然后传到 DLL 中做赋值操作.
分析执行过程, 首先 EXE 中的 m 初始化, 完成之后 m._Head._Parent 指向了一个 _Nil 节点. 然后这个 m 传到 dll 中. 此时, n 进行初始化, 又执行这样的代码:
if (_Nil == 0)
{_Nil = _Buynode(0, _Black);
_Left(_Nil) = 0, _Right(_Nil) = 0; }
注意, 在 DLL 中, 这里的 _Nil 为 0. 因为这个 _Nil 和 EXE 中的 _Nil 并不是同一份拷贝. 因此又会创建一个 node, 然后让 _Nil 指向它. 再让 n._Head._Parent 指向这个 _Nil.
问题在这里开始出现了. map 的代码认为其所有的实例的 _Head._Parent 都指向同一个 _Nil. 但这里已经违背了这个原则.
最终的结果就是 crash. 在这个例子中, crash 出现在 _Copy(_Nodeptr, _Nodeptr) 函数中.
注:原作中还有示例代码
转载自:http://blog.csdn.net/arcoolgg/article/details/1769612
评论:
Great article!分析得很准确!
话说回来,个人意见:避免类似的错,是应该养成良好的风格。
1)避免引用调用,而改用Const引用调用。没有任何理由使用非const的引用调用。
2)如果要取得某个值,使用值返回。
3)任何跨越Module边界的内存操作(分配/释放/copy),都应该避免。(MAP 的实现就是以这个假设,_buyNode已经分配内存,即使没有_Nil的错,在EXE里最后map释放的时候,也可能会Crash)
回到示例,似乎改为这样,就应该毫无问题:
MapDLL.cpp:
intmap MAPAPI func()
{
intmap n ;
n.insert(pair<int,int>(1,2));
return n;
}
MapEXE:
void main()
{
intmap& m = func();
}
原文2:STL跨平台调用会出现很多异常,你可以试试.