【问题标题】:Tell compiler not to execute destructor on my __thread variable告诉编译器不要对我的 __thread 变量执行析构函数
【发布时间】:2021-12-03 04:22:08
【问题描述】:

我特别希望我的一个变量上没有构造函数和析构函数。是这样定义的

struct Dummy { ~Dummy() { assert(0); } };
__thread Dummy dumb;

真正的代码是零初始化的,并从线程本地内存池中抓取内存。该池在 main 结束时被销毁,因此该析构函数尝试访问已释放的内存池。

我可以解决这个问题,但我想以一种干净的方式来解决这个问题。 1)我可以告诉编译器在所有线程本地(或全局)变量之后释放我的池吗?还是在所有线程本地函数之前对其进行初始化,以便在适当的时间进行清理?或者更好 2) 根本不执行清理?我可以发誓 __thread 不允许构造函数/析构函数,所以我很惊讶这竟然跑了。

堆栈跟踪显示__run_exit_handlers 是调用析构函数的函数。它的父母是exit、__libc_start_main 和start。这发生在 clang 和 gcc 上

【问题讨论】:

  • 顺便说一句,你为什么使用__thread 而不是thread_local? C与这有什么关系?这不是 C。
  • 这似乎是一个非常奇怪的要求。如果创建变量的线程已经死了,为什么还要让变量保持活动状态?
  • 您到底想通过不调用析构函数来防止发生什么?听起来像XY-problem。
  • @EricStotch 我一定是把问题读错了。 thread_local 变量将在创建它们的线程死亡时被销毁。 “池在 main 结束时被销毁,因此该析构函数尝试访问已释放的内存池” - 你在实现池或线程时做错了。如果线程从池中获取内存,它们肯定会在死亡时将其返回到池中吗?退出main之前所有线程都加入了吗?
  • @EricStotch 我认为正确修复它不会太难,但很难从这些描述中将所有内容拼凑在一起。也许如果您打开一个新问题并在其中输入minimal reproducible example,您将得到一个不需要破解的答案。 :-)

标签: c++ gcc clang


【解决方案1】:

在我得到答案之前,任何阅读本文的人都应该知道这通常是一个非常糟糕的主意,应该被视为最后的手段。但是,OP想要的是可行的,所以这里是:

析构函数可能是 C++ 语言中最可靠的东西。所以如果你想阻止一个对象被执行,你必须自己管理对象的生命周期和内存。

我们可以只 new Dummy 对象而不是 delete 它,但这会泄漏堆内存,这是不可取的。相反,我们希望在与线程本地对象本身关联的内存中创建 Dummy 对象,但绝不要销毁。

为此,要走的路是std::aligned_storage和placement new:

struct Dummy { ~Dummy() { assert(0); } };

struct DummyWrapper {
  DummyWrapper() {
    // Create the dummy
    dummy_ptr = new (&dummy_data) Dummy();
  }

  // Intentionally leave the destructor defaulted-out
  ~DummyWrapper() = default;

  operator Dummy&() { return *dummy_ptr; }

  std::aligned_storage_t<sizeof(Dummy), alignof(Dummy)> dummy_data;
  Dummy* dummy_ptr;
};

__thread DummyWrapper dumb;

DummyWrapper 对象将被构造和销毁,这是没有办法的。但是,Dummy 对象只会被创建,而不会被销毁,但底层内存管理仍然保持正常。

【讨论】:

  • 这是一个很好的答案。我认为对于我的具体情况,我可能会有另一个线程本地变量并将池标记为已死,因此它会立即返回。我真的希望我可以将一个变量标记为永不破坏:/
  • @EricStotch 这是一件好事。如此多的 C++ 是建立在可靠的析构函数之上的,如果绕过它们很简单,那么一切都会崩溃。
  • 是的!原来我还是傻了一半。我删除了手动调用析构函数的行,并且我的清理代码检测到我没有取消映射一些内存。我最终实现了early_cleanup(),因此我不会通过手动调用将再次执行的析构函数来混淆自己或其他任何人。尽管我无法在执行投票清理时提出请求,但看起来事情又恢复了正常
  • IDK,如果您碰巧知道,但它是否保证 main 所在文件顶部的线程本地将首先运行?如果是这样,我将能够让我的池首先/最后运行,并且能够删除一些代码
  • @EricStotch 这是一个值得单独提出的单独问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多