问题描述
在 Macos 上试用 snmalloc 我想知道为什么 all the created binaries are >256MiB。
事实证明,零初始化的 static inline
数据成员在 Mac OS X 上以一种奇怪的方式降低,在 ARM64 和 x86_64 上。即使是这个简单的测试也会产生巨大的二进制文件:
容器.h
#pragma once
#include <cstdint>
class Container {
public:
inline static uint8_t inner[256000000];
};
main.cc
#include "container.h"
int main() {
return Container::inner[0];
}
编译如下:
$ ~/clang+llvm-12.0.0-x86_64-apple-darwin/bin/clang -O3 -std=c++17 main.cc --target=x86_64-apple-darwin -c; ls -l main.o
-rw-r--r-- 1 hans staff 256000744 Jun 21 16:29 main.o
开源 clang 和 Apple clang 一样。 gcc 的行为类似。
在 Linux(使用 clang 或 gcc 编译)上,它包含在 .bss 部分中,因此不占用任何空间。
为什么 Macos 会出现这种情况?这是错误还是预期行为?
解决方法
我会继续尝试回答这个问题,尽管我会第一个承认,在您遇到一堵墙之前,您只能给出一个答案,“因为有人做出了决定并且你永远被它困住了。”
所有这些的主键以 MacOS 的 Mach-O 运行时规范的形式出现,该规范将 .bss
部分定义为用于:
未初始化的静态变量(例如,static int i;
)。
您可以在 10.3 版的此 archived version 中阅读相关信息,但您也可以在其他 Mach-O 参考资料中找到相同的信息。
这里要注意的重要一点是,bss
的使用仅指的是“私有”符号。换句话说,这指的是 static
关键字的 C 风格使用,该关键字保证是翻译单元的本地化。
当您将 C++17 成员变量声明为 static inline
时,尽管使用了反常重载的 static
关键字,但您已经创建了一个全局对象,其中保证只有成为程序中的一个实例。换句话说,使用此声明编译的每个翻译单元都将实例化它,并且期望链接器通过选择其中之一将它们“合并”为单个实例。这显然与 C 风格的“未初始化的静态变量”截然不同。
像clang这样的MacOS主机编译器通过将符号声明为weak
DATA
来实现这一点,例如类似于默认构造函数的声明方式(尽管那些当然会在TEXT
中) .
为了说明这一点,请注意,完全不使用 C++17 也可以获得相同的效果。例如编译这些示例集并查看程序集输出:
static uint8_t stuff[256000000]; // <- goes into .bss
int main() {
return (int)reinterpret_cast<uint64_t>(&stuff[0]);
}
请注意,在这种情况下,我必须在这里执行 &stuff
以确保编译器不会完全优化掉 stuff
。
现在试试这个:
uint8_t stuff[256000000]; // <-- goes into __DATA,__common
int main() {
return (int)reinterpret_cast<uint64_t>(&stuff[0]);
}
越来越近了。请注意,stuff
不会像您在 linux 平台上看到的那样放入 .bss
。再次根据 Mach-O 运行时规范,common
部分用于:
未初始化的导入符号定义(例如,int i;
)位于全局范围内(在函数声明之外)。”
现在试试这个:
__attribute__((weak)) uint8_t stuff[256000000]; // <-- in DATA,__data
int main() {
return (int)reinterpret_cast<uint64_t>(&stuff[0]);
}
这正是定义 static inline
C++17 成员变量的方式。在引擎盖下,clang 已将此符号指定为“合并”数据,在 x86 上它只是变成标准数据。如果你真的想深入了解香肠工厂,你实际上可以在 llvm SelectSectionForGlobal 函数中看到。
if (GO->isWeakForLinker()) {
if (Kind.isReadOnly())
return ConstTextCoalSection;
if (Kind.isReadOnlyWithRel())
return ConstDataCoalSection;
return DataCoalSection;
}
并且 DataCoalSection
被相应地定义为 here 与除 Power PC 之外的所有内容上的普通数据部分相同。
因此,从我的角度来看,鉴于 Mach-O 运行时的可用规范,您所看到的行为正如我所期望的那样。
,尝试为类实例化一个对象并从该对象调用成员。
Container obj;
cout << obj.inner[0];