HHK*_*HHK 17 c++ macos mach-o llvm clang
在Macos上尝试snmalloc 我想知道为什么所有创建的二进制文件都是 >256MiB。
事实证明,static inline
在 Mac OS X 上,ARM64 和 x86_64 上的零初始化数据成员以一种奇怪的方式降低。即使这个简单的测试也会产生巨大的二进制文件:
容器.h
#pragma once
#include <cstdint>
class Container {
public:
inline static uint8_t inner[256000000];
};
Run Code Online (Sandbox Code Playgroud)
主文件
#include "container.h"
int main() {
return Container::inner[0];
}
Run Code Online (Sandbox Code Playgroud)
编译成这样:
$ ~/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
Run Code Online (Sandbox Code Playgroud)
开源 clang 和 Apple clang 一样。gcc 的行为类似。
在 Linux(用 clang 或 gcc 编译)上,它包含在 .bss 部分中,因此不占用任何空间。
为什么在 Macos 上会出现这种情况?这是错误还是预期行为?
我会继续尝试回答这个问题,尽管我会是第一个承认你只能在遇到一堵墙上说“因为有人做出了决定而你永远坚持下去。”
所有这一切的主要关键在于 MacOS 的 Mach-O 运行时规范的形式,该规范将该.bss
部分定义为用于:
未初始化的静态变量(例如,
static int i;
)。
您可以在版本 10.3 的存档版本中阅读有关它的信息,但您也可以在其他 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]);
}
Run Code Online (Sandbox Code Playgroud)
请注意,我必须在&stuff
此处执行此操作以确保stuff
在这种情况下编译器不会完全优化。
现在试试这个:
uint8_t stuff[256000000]; // <-- goes into __DATA,__common
int main() {
return (int)reinterpret_cast<uint64_t>(&stuff[0]);
}
Run Code Online (Sandbox Code Playgroud)
越来越近。请注意,stuff
它.bss
不像您在 linux 平台上看到的那样放入。再次根据 Mach-O 运行时规范,该common
部分用于:
未初始化的导入符号定义(例如,
int i;
)位于全局范围内(在函数声明之外)。”
现在试试这个:
__attribute__((weak)) uint8_t stuff[256000000]; // <-- in DATA,__data
int main() {
return (int)reinterpret_cast<uint64_t>(&stuff[0]);
}
Run Code Online (Sandbox Code Playgroud)
这正是static inline
C++17 成员变量的定义方式。在引擎盖下,clang 已将此符号指定为“合并”数据,在 x86 上它只是变成标准数据。如果你真的想深入了解香肠工厂,你实际上可以在 llvm SelectSectionForGlobal函数中看到。
if (GO->isWeakForLinker()) {
if (Kind.isReadOnly())
return ConstTextCoalSection;
if (Kind.isReadOnlyWithRel())
return ConstDataCoalSection;
return DataCoalSection;
}
Run Code Online (Sandbox Code Playgroud)
并且在此处DataCoalSection
相应地定义为与除 Power PC 之外的所有内容上的普通数据部分相同。
因此,从我的角度来看,鉴于 Mach-O 运行时的可用规范,您所看到的行为正如我所期望的那样。
小智 -2
尝试实例化该类的对象并从该对象调用成员。
Container obj;
cout << obj.inner[0];
Run Code Online (Sandbox Code Playgroud)