【问题标题】:Specify construction/destruction order of static locals in different accessors指定不同访问器中静态局部变量的构造/销毁顺序
【发布时间】:2013-12-19 05:45:20
【问题描述】:

我在 cxa_finalize 运行程序时遇到崩溃(这是一个程序,而不是其中的库):

$ ./ac-test.exe 
Assertion failed: AcLock.cpp(54): AcLock
libc++abi.dylib: terminate called without an active exception
Abort trap: 6

断言/崩溃是由于对象和记录器之间的交互造成的。记录器在对象之前被破坏,但对象使用记录器。因此,弹出断言或崩溃的正在获取的互斥锁已经被破坏(因此pthread_mutex_lock 锁定记录器时失败的原因)。

我已经阅读了 Specifying Attributes of VariablesDeclaring Attributes of Functions 的 GCC 手册,但我显然遗漏了一些东西。

我将对象和记录器放在访问器中的一个公共标头中,并尝试指定构造顺序:

// AcGlobals.h
static AcLogger& GetLogger() {

    static AcLogger logger __attribute__(init_priority(50));
    return logger;
}
static AcSocketList& GetAcceptSockets() {

    static AcSocketList sockets __attribute__(init_priority(100));
    return sockets;
}

这导致了一堆错误:

./AcGlobals.h:24:46: error: expected ';' at end of declaration
    static AcLogger logger __attribute__((init_priori...

./AcGlobals.h:24:47: warning: declaration does not declare anything
      [-Wmissing-declarations]
    static AcLogger logger __attribute__((init_priori...

我也尝试将属性放在函数而不是变量上:

// AcGlobals.h
static AcLogger& GetLogger() __attribute__(init_priority(50)) {

    static AcLogger logger;
    return logger;
}
static AcSocketList& GetAcceptSockets() __attribute__(init_priority(100)) {

    static AcSocketList sockets;
    return sockets;
}

这导致了更多问题:

./AcGlobals.h:22:53: warning: GCC does not allow init_priority attribute in this
      position on a function definition [-Wgcc-compat]
static AcLogger& GetLogger() __attribute__((init_priority(50))) {
                                              ^
./AcGlobals.h:22:53: error: can only use 'init_priority' attribute on file-scope
      definitions of objects of class type

我也尝试了__attribute__((constructor(50))) 而不是init_priority,但没有任何乐趣。

注意:我正在使用 Apple 机器。 Apple 有一个“功能”,其中构造函数优先级仅适用于同一文件中的修饰函数和变量。所以这些不能分散在翻译单元中。

如何准确地指定本地静态对象的构造和销毁顺序?

【问题讨论】:

  • 你使用的是 GCC 还是 Clang?
  • 我两者都用,但以上来自源代码构建的 Clang 3.3。 (我还使用 ICC 和 VS 来尝试删除所有实现定义和未定义的行为)。
  • 明智的解决方案是获得更少的全局变量。由于您无法轻松控制全局变量的构建(和销毁)顺序,因此与其跳过无尽的圈子试图让编译器按照您想要的顺序执行此操作,不如创建依赖于其他全局变量的全局变量.
  • @jalf 如果你看一下代码......他的问题不是初始化顺序;他使用 Meyers 单例成语来解决这个问题。这是破坏顺序(因为他的错误在cxa_finalize,它是在程序结束时调用析构函数的g++函数)。

标签: c++ gcc initialization clang


【解决方案1】:

这是使用 Meyers 单例时的经典问题 (这基本上就是你正在做的事情)。解决方案是 破坏单例;而不是静态的本地 变量,您应该使用不带删除的动态分配:

static  AcLogger& GetLogger()
{
    static AcLogger* logger = new AcLogger;
    return *logger;
}

请注意,在这种情况下,您必须确保每次使用 记录器刷新(但无论如何通常都是这种情况); 否则,您可能会得到未刷新的数据。

关于您尝试使用您的扩展功能 编译器:我不太熟悉它,但我不明白你如何 可以在局部变量上使用名为 init_priority 的东西。 局部静态的构建(和销毁)时间 变量由语言定义(在这种情况下, 销毁时间不是你想要的)。如果你想使用 这个非标准的扩展,你可能不得不使 实例变量是一个静态类成员,甚至可能是一个全局变量 (在这种情况下,您不能将构造函数设为私有)。

【讨论】:

  • @Nim 是的。我会添加它。
  • 谢谢詹姆斯。这很有趣 - 更喜欢内存泄漏而不是崩溃;)它可能是较小的弊端。当程序关闭时,如何确保记录器刷新?我依靠dtor 进行最后的冲洗。
  • @noloader 这并不是真正的内存泄漏。至于flush……我一般是通过一个本地设计的类来访问logger对象,保证flush;您通常希望在日志中的每条记录之后刷新,以便在代码崩溃时获得最新信息。否则,您需要确保析构函数中的所有日志请求都在析构函数中刷新。
【解决方案2】:

假设依赖是非循环的,您可以按照代码流进入函数的顺序利用标准的初始化行为,并按照初始化的相反顺序进行销毁。

换句话说,调用 GetLogger() 来初始化记录器,然后调用 GetAcceptSockets() 来初始化列表。这将导致套接字列表首先被破坏(当记录器仍然存在时),然后记录器最后被破坏。

【讨论】:

  • 谢谢科尔宾。 “非周期性” - 是的,没有循环依赖。我也厌倦了“在main 中首先调用GetLogger”技巧。它没有用。
  • @noloader 嗯,clang 通常非常符合标准。我想知道它的优化器是否过于热心并摆脱了调用。如果你在禁用优化的情况下编译,它会以正确的顺序破坏吗?
  • 以上是-DDEBUG=1-O0-g3-ggdb的调试配置。在发布配置中,日志的尾部被践踏。是的,Clang 很棒(尤其是 3.3 及其消毒剂)。
  • @noloader 你确定程序除了销毁命令之外运行正常吗?您是否尝试过添加一些简单的std::cout 样式调试来查看它们的破坏顺序?
  • 是的,我很确定。该项目还很年轻,我正在慢慢添加功能。所以目前只有很少的活动部分:大约 6 个构造函数,记录函数调用,以及对 OpenSSL 的各种 init 函数的调用(我还没有创建套接字)。但我确实找到了一种解决方法:(1)使用带有初始化优先级的constructor属性,(2)添加一个cpp文件,(3)在h文件中声明constructor属性,以及(4)在cpp 文件中实现。
猜你喜欢
  • 1970-01-01
  • 2013-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-12
  • 1970-01-01
相关资源
最近更新 更多