【问题标题】:Shouldn't I use _endthreadex() in thread procedure for stack unwinding?我不应该在线程过程中使用 _endthreadex() 进行堆栈展开吗?
【发布时间】:2012-02-01 03:00:30
【问题描述】:

我在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;
}

输出结果:
虚拟角色
虚拟角色
虚拟角色
虚拟 dtor
分配不当
虚拟机

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

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

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

【问题讨论】:

  • 如你所知,构造函数和析构函数的输出不是配对的。 不,我们不知道,什么意思?
  • @Als 仔细看看。有三个构造函数调用和两个析构函数调用。
  • @Als 对不起我的英语不好。这句话的意思是构造函数输出的行数与析构函数的不成对。

标签: c++ stack-unwinding


【解决方案1】:

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

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

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

【讨论】:

  • 感谢您分享您的知识。我已经知道你告诉我什么了。但是,在 _beginthreadex() 的 MSDN 示例中,线程过程在过程结束时显式调用 _endthreadex()。你知道显式调用是什么意思吗?
【解决方案2】:

你的问题是:

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

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

堆栈展开,清除该范围内的所有本地对象,

Dummy dm;

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

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

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

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

【讨论】:

  • 据我所知,堆栈展开一直持续到遇到正确的异常处理语句,例如'catch(...)'。在这种情况下,我预计 InnerFunc() 中的 bad_alloc 异常将在 ThreadFunc() 中处理。所以,我认为调用堆栈中两个函数之间的所有函数都受到了影响和展开。对吗?
  • @KyokookHwang:是的,你是对的,在范围内的堆栈上创建的所有本地对象都将被展开。再想一想我可能误解了你的 Q,我的思绪跳到了动态内存分配/释放,而我的回答试图解释说您可能已经知道了?
  • 是的。感谢您提供有关管理内存资源的信息。这些信息让我想起了这个话题。再次感谢您。
猜你喜欢
  • 2017-06-10
  • 1970-01-01
  • 1970-01-01
  • 2016-07-02
  • 2012-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-29
相关资源
最近更新 更多