本人在工程中遇到一个极其刁钻古怪的问题,描述起来比较困难,我尽力说清楚。。开发环境:VC6SP6
运行环境:Win2003
工程概括:DLL,SGI_STL,SEH,单线程,no-mfc,win32,Mutltithread-DLL,自建堆,....工程十分庞大,以下列举极小的一部分。
在一个非常频繁调用的DLL某函数中创建vector,并通过虚基类虚函数调用另一个DLL的GetVector函数获得vector的内容,实现如下://公共头文件:
typedef std::vector<int> VEC_INT;
const int _MAX_VEC = 100;// 虚基类ClassB (DLLB.dll) 函数void GetVector(VEC_INT& rVec) =0; 实现:
// 注:ClassB有许多实例,通过自建堆管理。ClassA调用时会先找到指定的ClassB的实例
void GetVector(VEC_INT& rVec)
{
    // SEH异常处理略
    VEC_INT::iterator iter = my_vec.begin();
    int i = 0;
    for (; iter!= my_vec.end() && i != _MAX_VEC; iter++; i++)
    {
        rVec.push_back(*iter); // Mark1
    }    // rVec = my_vec; // Mark2
}// 类ClassA函数(DLLA.dll)调用ClassB函数的接口,(实际上是通过另一个类ClassC中转的,反正实际调用的就是ClassB的函数)
VEC_INT vec;
// vec.reserve(_MAX_VEC)  // Mark3
c->GetVector(vec);// 之后可以对vec进行各种操作
至此,工程在较小的压力下可以正常运行,没有出现任何问题。
但是当工程压力非常大,调用非常频繁时,Mark1和Mark2 2种写法都会造成程序崩溃。用SEH和标准c++异常捕捉均告失败。只有在catch(...)的时候能捕获到异常,得到此时i = 8,但是什么异常就不得而知了。
内存4G,使用量为60%左右。跟踪这个问题跟踪了很久,发现在Mark3的地方加上reserve就可以正常运行,不会再出现崩溃现象。百思不得其解!基本排除工程其它地方出错导致堆破坏之类的问题。
找来SGI_STL的源代码看了半天,也没发现有什么严重问题。
顺便说一下SGI_STL的内存管理,SGI_STL在<=128字节的内存分别以8字节为单位建立8*16指针数组,每个数组对应一个相应内存大小的链表,如__my_free_list[0]就是一个8字节的内存链表,__my_free_list[15]就是一个128字节的内存链表。当分配小额内存时自动创建20个这种小额空间到__my_free_list,空间释放时小额内存被添加到链表头部,空间不够时继续创建该空间,大于128的内存空间采用标准释放分配策略。内存管理函数为malloc和free,在默认堆中进行。
有点怀疑是malloc和自建堆的空间产生冲突,导致每次vector翻倍成长时内存分配与自建堆冲突,不过觉得windows应该不存在这种严重的bug吧。。而且reserve使用的也是相同的SGI内存分配函数,只不过一开始就分配足够大的内存(不超过128),省略了vector的增长时malloc和链表扩大部分。
自己新建了一个工程,用主线程显式加载测试DLL,实现如下://主线程:
#include <iostream>
#include <vector> //不include windows,是因为SGI_STL会包含。
#define _MAX_VEC 10000
#define _MAX_LOOP 100
typedef std::vector<int> VEC_INT;
typedef void (*FUNC_MYHEAP)(VEC_INT& , int);void testSEH(FUNC_MYHEAP MyHeapTest)
{
    for (int x= 0; x < _MAX_LOOP; x++)
    {
VEC_INT vec[_MAX_VEC];
for (int i = 0; i < _MAX_VEC; i++)
{
__try
{
MyHeapTest(vec[i], 16);
}
__except(puts("in filter"), 1)
{
int err = GetExceptionCode();
cout << err << endl;
cout << &std::__default_alloc_template<true, 0>::_S_start_free << endl;
cout << &std::__default_alloc_template<true, 0>::_S_end_free << endl;
cout << std::__default_alloc_template<true, 0>::_S_free_list[0] << endl;
cout << std::__default_alloc_template<true, 0>::_S_heap_size << endl;
}
}

    }
};void main()
{
HMODULE hMoudle = LoadLibrary("testDLL.dll");
if (hMoudle == NULL)
{
return;
} FUNC_MYHEAP MyHeapTest = (FUNC_MYHEAP)GetProcAddress(hMoudle, "TestMyHeap");
if (MyHeapTest == NULL)
{
return;
} testSEH(MyHeapTest);
}
//DLL,本来想用自建堆,不过想想没什么必要,反正随便测试的,名字也就保留这样了。
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif#include <vector>typedef std::vector<int> VEC_INT;extern "C" DLL_API void TestMyHeap(VEC_INT& vec, int nSize);
void TestMyHeap(VEC_INT& vec, int nSize)
{
for(int i = 0; i < nSize; i++)
{
vec.push_back(i);
}
}测试发现一切正常,不过因为栈空间的原因,vec不能一次分配过大。谁知道工程中可能出现的问题有哪些?拜谢了!

解决方案 »

  1.   

    我认为不是stl也不是window的问题,可能是多线程同步访问数据的问题.
    操作Vector数据内容的时候,没有使用临界区对数据进行保护?
      

  2.   

    sgi_stl有用宏定义判断是否多线程,然后使用自建的锁类 _Lock __lock_instance;
    不过在帖子中我已经明确声明该工程是单线程,并且SGI事实上是通过多线程的运行方式处理的。也就是说不可能存在上述情况。
      

  3.   


    宏不是运行时代吗,是由编译时开关所决定的。如果没有定义打开多线程支持的宏。那么stl仍然是没有多线程保护的!我还有些疑问要请教楼主:
    1.ClassB (DLLB.dll)中的my_vec和所有操作包含添加、删除的操作和调用ClassA函数(DLLA.dll)确定是在同一线程?可以在函数里log一下有多少中线程id访问过。
    2.楼主可以独立写一个Sample exe,单线程操作,并且也用大压力来测试操作stl,我认为这样一定是没有问题的。
    综上所述,我还是坚持我的判断。
      

  4.   

    说明一下,虽然工程中打开了/MTd开关,不过这是为了支持STL,并且该工程没有创建过其他线程(不排除被其他线程调用的可能性)
    并且我说的 _Lock __lock_instance是SGI对自己的内存分配管理用的线程锁。1. 是的,绝对没有被其他线程访问过。首先my_vec是私有变量,只能通过函数访问。而调用访问函数的只可能是我自己工程使用的DLL
    2. 我确实写过这个test程序,看我code第二部分。
      

  5.   

    再说一下,如果是因为多线程产生的冲突,也不可能会在vec.reserve(_MAX_VEC);后恢复正常,再也没有出现过问题。
      

  6.   


    /MTd表示的是使用支持多线程的运行库(runtime-library),但是这个和stl本身是不是支持多线程无关
    1.虽然说调用访问函数的只可能是楼主工程使用的DLL,但是楼主能保证使用DLL的函数一定是同一个线程?我觉得这里最好还是Log一下,然后看看是不是多线程的问题
    2.你写的test程序没有问题吧,没有出任何错误吧?stl经过这么用户的基本上不会有什么错,大多数是我们的用法有问题。test程序没问题,而楼主的工程大压力下有问题,所以我的看法还是很可能是多线程同步访问数据的问题
      

  7.   


    我认为这是由于vec.reserve(_MAX_VEC);后内存足够大了,不需要重新分配,所以才没有出现问题。出问题的地方正是某个地方正在重新分配内存的时候迭代器被拿去使用。
      

  8.   

    看不出什么问题,如果硬要说,只能说楼主的帖子里
    "for (; iter!= my_vec.end() && i != _MAX_VEC; iter++; i++)"
    这一句有3个分号...附上一些stl使用心得:
    1、for循环的测试条件最好在语句外面拷贝出来,一则避免反复调用函数(比如xxxvec.end())去读该值,二则避免循环体内意外的更改该值。因为不等于测试容易跳码,例如
    这个函数里的for循环,你能看出问题来吗?
    void _2dext_list_clear(LIST* list){
    for(int i=0;i!=list->curLength;++i){
    _2dext_list_pop_front(list);
    }
    }
    问题在这一句
    i!=list->curLength;
    for语句在这一句为真时执行,但curLength会被循环体改变,i的变化与curLength的变化
    0 1 2 3 (i)
    4 3 2 1 (curLength)
    0 1 2 3 4 5 6(i)
    7 6 5 4 3 2 1(curLength)
    可以看出curLength为4时,循环都会在i==2时中断掉,清空函数_2dext_list_clear不能释放掉链表中最后两个元素。curLength为偶数时,循环会在curLength/2时断掉,curLength为奇数时,越来越大的i值不停的与0测,结果是陷入死循环。楼主把iter!= my_vec.end()这一句改为endit=my_vec.end();iter!=endit;试试2、在stl里使用struct的时候注意不要做看似聪明的事情
    struct VERTEX{
    float x,y,z;
    };
    vector<VERTEX>存进去的东西都是0 ??
    答:是你做这个vec的时候写了大小,比如
    vector<VERTEX> vecVertex(vertexNum);
    指定大小没问题,但这样写会去调用VERTEX的默认构造函数去赋初值,而struct是结构,并不是所谓的纯public class,也就没有构造函数的概念,虽然编译没有问题,但系统内部会做出一个拷贝构造函数,像下面这个样子
    VERTEX::VERTEX(const VERTEX& source){
    x=0;
    y=0;
    z=0;
    }
    可以看到,无论source怎么变,赋进去的值都是0这一条和楼主所说正好相反,楼主试试不用typedef声明vector<int>的情形,并且自己实现一个vector<int>的拷贝构造函数试试3、把楼主代码里vec.reserve(_MAX_VEC)移入GetVector里试试
      

  9.   

    加上reserve之后不出错,问题应该是与内存分配和释放有关,有可能内存管理部分有bug,也有可能是(虚拟)内存不足,导致分配内存失败。
      

  10.   

    for (; iter!= my_vec.end() && i != _MAX_VEC; iter++, i++)
    应该是这样的,编辑的时候写错了。。我的工程所有dll都是被一个主dll显示加载的,并且不会被其他的dll显示加载。所以基本上可以确定这些dll在同一个线程中。
    vector里面传入的是int类型数据,所以不存在此类问题。并且我的循环体内只有赋值,没有修改条件容器的结构。链表操作是SGI负责的东西,而且SGI很好地实现了多线程操作(stl_alloc.h)。内存分配失败个人感觉不太可能,用GlobalMemoryStatusEx_获得内存大概是2g多 / 4g,应该不会有问题。。就算是内存分配失败,SEH也能捕捉到异常吧。不过这里的异常我根本捕捉不到。btw,就算是多线程在更改容器地址时另一个线程的iterator在访问,因为访问区域是合法堆区域,所以最多只会造成数据异常,而不是造成程序崩溃。而就算某些时候访问到非法内存区域,也应该会被SEH捕获到吧。PS: 如果出现崩溃被catch(...)捕获,则以下所有操作都会造成异常被catch(...)捕获。