0

我检查了有关 win32 环境中线程过程的堆栈展开。
我的测试代码如下。

class Dummy
{
public:
    Dummy() { wcout << L"dummy ctor" << endl; }
    ~Dummy() { wcout << L"dummy dtor" << endl; }
};

void InnerFunc()
{
    Dummy dm;

    while(1)
    {
        char *buf = new char[100000000];
    }
}

unsigned WINAPI ThreadFunc(void *arg)
{
    Dummy dm;

    try
    {
        InnerFunc();
    }
        catch(bad_alloc e)
    {
        wcout << e.what() << endl;
    }

    _endthreadex(0);
    return 0;
}

void OuterFunc()
{
    Dummy dm;

    HANDLE hModule;
    hModule = (HANDLE)_beginthreadex(0, 0, ThreadFunc, 0, 0, 0);
    WaitForSingleObject(hModule, INFINITE);
    CloseHandle(hModule);
}

int _tmain(int argc, _TCHAR* argv[])
{
    OuterFunc();
    wcout << e.what() << endl;

    return 0;
}

输出结果:
dummy ctor
dummy ctor
dummy ctor
dummy dtor
bad allocation
dummy dtor

如您所知,构造函数和析构函数的输出不是配对的。我认为 _endthreadex() 使线程句柄发出信号并跳过线程的堆栈展开。

当我在没有 _endthreadex() 的情况下再次测试时,我能够得到我期望的结果。

在这种情况下,如果我需要在线程上展开堆栈,我不应该在线程过程中使用 _endthreadex() 吗?

4

2 回答 2

2

我猜想在 ThreadFunc 中创建的实例永远不会调用析构函数。但是,您应该添加一种方法来区分每个构造函数和析构函数调用以确保。

假设这就是正在发生的事情,很明显 endthreadex 立即终止线程而不清理堆栈。文档明确声明 endthreadex 在 ThreadFunc 返回时被调用,那么为什么还要在这里显式调用它呢?

这绝对是我会使用 boost::thread 的情况。它会在线程创建和清理方面做正确的事情,而不会让您担心特定于 win32 的细节。

于 2012-02-01T03:27:52.823 回答
1

你的问题是:

while(1)
{
    char *buf = new char[100000000];
}

您已经创建了内存泄漏,在每次迭代中,您都会创建一个新对象,丢失对旧对象的任何引用。

Stack Unwinding,清除该范围内的所有本地对象,

Dummy dm;

是分配在本地存储内部的对象InnerFunc(),堆栈展开正确地销毁了该对象,您看到的单个析构函数调用跟踪就是由于这个原因。

堆栈展开不会显式释放动态内存。每个分配的指针都必须通过在同一地址上 new[]调用 a 来显式释放。delete []

我看不出它与任何 Windows 线程函数有什么关系(我对 Windows 不太感兴趣),但正如我已经说过的那样,你在那里有问题。

解决方案:
在异常期间处理清理的简单解决方案是RAII
您应该使用智能指针来包装原始指针,然后智能指针确保您的对象内存在作用域结束后得到适当的释放。

于 2012-02-01T03:03:38.740 回答