如何比任何其他全局或静态变量更早地创建/构造类实例?

Posted

技术标签:

【中文标题】如何比任何其他全局或静态变量更早地创建/构造类实例?【英文标题】:How to create/construct a class instance earlier than any other global or static variable? 【发布时间】:2018-02-08 00:28:43 【问题描述】:

如何在 main() 之前创建特定类的实例,早于任何其他实例(包括静态和全局变量,包括在静态和动态库中(包括第三方提供的库))?


我正在处理一个包含多个类的项目,这些类在构建过程中可能会导致错误,例如访问 NULL 指针。任何此类错误都会导致向应用发送信号。我有一个信号处理程序,可以捕获信号,显示违规线程的堆栈跟踪,并调用导致生成核心转储的默认信号处理程序等。 然而,一些这样的导致错误的实例被创建为类的全局变量和静态变量。 IE。它们被构造并导致输入早于 main() 的信号。

要捕获此类信号,我需要在 main() 之前注册我的信号处理程序,即我需要创建一个实例(将注册信号处理程序)也作为全局或类静态变量,我需要保证此类实例的创建/构造早于任何其他实例。

如何做到这一点?


要注册一个信号处理程序,我使用sigaction()。 为了显示堆栈跟踪,我使用了backtrace()、backtrace_symbols()、abi::__cxa_demangle()。

【问题讨论】:

你能重构代码以使用“Construct on First Use Idiom”而不是使用全局变量吗? 【参考方案1】:

标准 C++ 不提供在翻译单元之间对初始化程序进行排序的方法,但 gcc 可以。来自https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html的示例:

Some_Class A __attribute__((init_priority (2000)));

最低值(最高优先级)是 101。

【讨论】:

我自己没有用过,但也许 Win32 PE 格式的部分也可以是一个解决方案。见docs.microsoft.com/en-us/cpp/preprocessor/init-seg @Todd Fleming,我使用了__attribute__ ((init_priority (101))),看起来它只适用于可执行文件,动态/共享库中的全局/静态仍然比__attribute__ ((init_priority (101))) 更早初始化。所以你的解决方案部分解决了这个问题。 @RobinKuzmin 很不幸。我猜 gcc 的假设是可执行文件依赖于库,而不是相反。【参考方案2】:

调用静态构造函数的顺序取决于它们在源文件中的顺序、链接顺序和加载共享库的顺序。通常的做法是使用注册函数创建一个单独的文件,并确保相应的对象在链接器命令行中显示为第一个。它应该是任何共享或静态库的一部分。

即第一.c

static int first_function() 
    // register your signals
    ...
    return 0;

static int status = first_function();


gcc -c first.c -o first.o
gcc -o myexec first.o ... all other files

【讨论】:

【参考方案3】:

您可以在主应用程序之外创建一个小项目或可执行文件,在其自己的解决方案中单独编译和构建。然而,这个小应用程序将有一个必需的命令行参数,以便它与您想要测试的主应用程序一起工作。命令行参数将是主项目的文件夹路径或目录所在的路径,其中将包含与此外部应用程序一起使用的头文件。然后根据您使用的 IDE,您必须对 IDE 进行一些配置以设置正确的路径。

例如,如果您在 MS Visual Studio 中工作。您可以右键单击您的启动项目并选择属性,然后在左侧的属性页面上有一个名为Build Events的部分,然后如果您展开此部分,它会给您三个选择:Pre-Build EventPre-Link Event & @ 987654324@ 所有这三个在其设置的右侧都有相同的字段:Command Line 你必须在其中设置路径,Description 任何你想用来描述构建事件命令的地方,以及Use In Build这基本上是Y/N 的布尔切换。

现在,其他 IDE 在配置方式和可用选项方面会有所不同,但这种性质的某些东西可能会满足您的需求。请原谅我上面使用 Microsoft Visual Studio 的示例,因为我不熟悉 Linux。但是,我确实相信可以做类似的事情;只需了解您的编译器、链接器和调试器即可。

【讨论】:

这似乎与问题完全无关。 @BenVoigt 也许;但我做了类似的事情,我使用一个外部小应用程序为当前正在运行的主应用程序预配置数据。【参考方案4】:

使用静态函数变量可能会有一个解决方案。

class A;

A & GetA()

    static A internal_a;
    return internal_a;


...

class B

    public:
    B()
    
        auto &a = GetA();
        a.DoSomething();
    


...

B global_b;

...

每当调用 B 的构造函数时,通过调用“GetA”,静态函数变量“internal_a”在进一步使用之前被初始化。

另见What is the lifetime of a static variable in a C++ function?

Scott Meyers 的“Effective C++”一书中也描述了这种技术。

【讨论】:

【参考方案5】:

(对我自己的问题提供一个临时答案) 基于

this answer@Serge, this answer@ToddFleming, 和this我自己的观察

最终的答案似乎是

在 .CPP 文件中创建全局实例, 将__attribute__ ((init_priority (101))) 添加到该实例, 在动态/共享库的链接器列表中首先列出该文件, 构建动态/共享库, 首先加载该库,然后再加载所有其他动态/共享库。

此解决方案尚未完全检查(希望稍后我会检查)。


如果您决定为这个答案投票,那么最好为我上面提到的其他答案投票(或至少除此之外)。

【讨论】:

【参考方案6】:

很遗憾,这是不可能的,除非您可以访问源代码(因为您拥有 3rd 方库,所以排除了这一点)。

如果你这样做:

// Foo.h
inline fooGlobal()  static Foo f; return f; 
static auto& foo = fooGlobal();

那么您可以保证包含 Foo.h 的任何文件都会在该文件中的任何全局变量之前看到 foo 初始化。如果您的第三方库仅在头文件中定义全局变量(使用类似于上述的技巧来防止获得多个全局变量),您可以确保在所有 TU 中,在第三方头文件之前包含 Foo.h。但是,如果第三方库在 .cpp 文件中定义了全局变量,那么就纯 C++ 而言,您绝对无能为力,不敢破解该代码。

现在,如果你真的很绝望,你可以做一件事:你可以在一个共享库中定义你的全局,并使用 LD_PRELOAD 技巧确保你的共享库在其他任何东西之前被加载:What is the LD_PRELOAD trick?。这很 hacky,但如果它是一个你可以完全控制的二进制文件(即你没有将它分发给客户端;也许只是你自己运行的服务器)那么这可能就足够了。

【讨论】:

以上是关于如何比任何其他全局或静态变量更早地创建/构造类实例?的主要内容,如果未能解决你的问题,请参考以下文章

构造函数用于创建类的实例对象,构造函数名应与类名相同,返回类型为void.

单例模式

实例构造函数与静态构造函数执行顺序

详谈单例饿汉和懒汉模式

c#静态变量和非静态变量的区别

Java: static的使用