保证内联静态 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 脚本中引用它可能就足够了。
我继承了一些在 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 脚本中引用它可能就足够了。