保证内联静态 Meyers 单例的对象生命周期,并明确放置在一个部分中?

Guarantees on the Object lifetime of inline static Meyers singleton, with explicit placement in a section?

我继承了一些在 g++ 9 和 10 下编译良好的代码,但是当打开优化时,两个编译器都会出现运行时错误(也就是说,编译 -O0 有效,但编译 -Og 会出现运行时错误MMU。)

问题是在 class 的内联静态方法中定义了一个 Meyers 单例,并且该对象似乎被优化掉了。方法中的静态对象是使用部分属性声明的,这很复杂(这是 g++ 语言扩展,用于将选项放置在目标文件的特定部分中。)

这里总结一下情况。

文件c.hpp


namespace my_prod {

   class C {
   // C has a default c'tor
   public:
   static C& instance() {
      static C c __attribute__((section("MY_C_SECTION")));
      return c;
   }

   void f();
   };
}

文件c.cpp

#include "c.hpp"

using namespace my_prod;

void C::f() {
   // implementation of f, doesn't use instance()
}

文件p.hpp

namespace my_prod {

   class P {
   public:
   static void g();
   };
}

然后归档p.cpp

#include "p.hpp"
#include "c.hpp"

using namespace my_prod;

void P::g() {
    C::instance().f();
}

链接描述文件包括:

MEMORY
{
   BIG_CHUNK (rw) : ORIGIN = <address>, LENGTH = <enormous>
}

SECTIONS
{
   .my_space (NOLOAD) :
   {
      . = ALIGN(32);
      *(MY_C_SECTION)
   } > BIG_CHUNK
}

对于两个优化级别,objdump -C -r -t p.o 给出

00000000  w    O MY_C_SECTION    00002220 my_prod::C::instance::c

(即,不是局部的,不是全局的,但它很弱。)

但是 elf 文件上的 objdump 在优化为 -O0 时显示 BIG_CHUNK 中的符号,但在优化为 -Og 时丢失。

项目定义以下开关可能是相关的::

-ffunction-sections
-fdata-sections
-Wl,--gc-sections

尽管这些开关始终适用于所有构建。

解决方案是将 my_prod::C::instance() 方法的定义移动到 c.cpp 中。然后该符号在本地定义并且不再弱,并且无论优化级别如何都出现在最终的精灵中。

我的问题是,解释这种行为的 C++ 规则是什么?

GCC 使用 COMDAT section groups when available to implement vague linkage。尽管被显式命名为 MY_C_SECTION,编译器仍然发出一个以 _ZZN7my_prod1C8instanceEvE1c 作为键符号的 COMDAT 组:

    .section    MY_C_SECTION,"awG",@progbits,_ZZN7my_prod1C8instanceEvE1c,comdat

我希望您对 MY_C_SECTION 的其他用途 而不是 具有相同签名符号的 COMDAT 组的一部分。这会造成 link 编辑器对节垃圾回收的模棱两可的情况。

在没有优化的情况下,编译器发出对 _ZZN7my_prod1C8instanceEvE1c 签名符号的引用,碰巧在这个 linker 垃圾回收的特定实现中,这足以保留 MY_C_SECTION 节和 _ZZN7my_prod1C8instanceEvE1c 符号定义。通过优化,该引用消失了,并且由于情况不明确,link 编辑器丢弃了定义。

没有 my_prod::C::instance() 的内联定义,不涉及模糊的 linkage 和 COMDAT 组,因此不会出现歧义,linker 脚本按预期工作.

为了在保留内联定义的同时解决这个问题,使用唯一的部分名称而不是 MY_C_SECTION 并在 linker 脚本中引用它可能就足够了。