C++ 运行时是否总是需要 malloc()?

Does C++ runtime always require malloc()?

我有一个 C++ 应用程序 运行 裸机,我想使其尽可能小。

我没有在任何地方使用动态内存分配。我没有使用任何 STL 函数。我还用空函数覆盖了 "delete" 和 "new" 的所有变体。尽管如此,当我查看经过排序的符号列表时,我发现 malloc() 仍然是我编译的二进制文件中最大的项目之一。如果我能摆脱它,我可以将我的二进制文件缩小大约 25%。

C++ 运行时是否通常需要 malloc() 进行幕后类型工作?

(我正在为 Microblaze 架构使用 Xilinx 的 gcc 分支,如果这很重要的话)

如果您使用任何 STL,例如 std::libstd::map。甚至std::cout,背后还会有动态内存分配

它总是需要malloc。因为必须加载二进制文件以及共享库。

程序对 malloc() 的依赖可能发生在 C 和 C++ 中,即使程序不直接使用它们。这是编译器和标准库的实现质量问题,而不是标准的要求。

这实际上取决于编译器启动代码(进行设置以便 main() 可以被调用的代码)的工作方式以及标准库代码的实现方式。

例如,在 C 和 C++ 中,启动代码(在托管环境中)需要收集有关命令行参数的信息(可能复制到某个分配的缓冲区),连接到标准 files/streams(如 [= C++ 中的 12=] 和 std::cin,以及 C 中的 `stdout 和 stdin)。这些事情中的任何一个都可能涉及动态内存分配(例如,用于与标准流关联的缓冲区)或执行程序实际不需要的代码。

C++ 有两种实现方式,托管的和独立的。托管实现确实假设 malloc 存在并且经常将其用于内部目的。独立实现假定仅存在 new 函数,因为它支持 C++ 关键字 new,但很容易确保不会调用此函数。

两者的区别在于,在独立实现中,您可以控制程序启动,所需的 headers 和库集是有限的。通过设置入口点来控制程序启动。

g++ -ffreestanding -e _entry program.cpp

program.cpp 可能是:

extern "C" int entry()
{
    return 0;
}

extern "C" 是防止 C++ 名称混淆所必需的,这可能会导致在链接期间难以弄清楚 entry 的名称是什么。然后,不要使用newstd::string、流I/O、STL之类的,避免使用at_exit.

这应该可以让您控制启动和清理代码,并限制编译器可以隐式依赖标准库中可用的内容。但是请注意,这可能是一个具有挑战性的环境。您不仅会阻止初始化堆、I/O 流等,还会阻止设置异常、RTTI 和调用静态存储 object 构造函数等。您将不得不编写代码或使用库来手动选择您可能想要使用的 C++ 的几个功能。如果你走这条路,你可能想仔细阅读这个 wiki http://wiki.osdev.org/C%2B%2B。您可能至少需要调用全局构造函数,这很容易做到。

说明

标准

C++ 有一个 "freestanding implementation" 的概念,其中可用的 headers 较少。

3.6.1 主要功能[basic.start.main]

1 [snip] It is implementation-defined whether a program in a freestanding environment is required to define a main function.

17.6.1.3 独立实施 [合规]

1 Two kinds of implementations are defined: hosted and freestanding (1.4). For a hosted implementation, this International Standard describes the set of available headers.

2 A freestanding implementation has an implementation-defined set of headers. This set shall include at least the headers shown in Table 16.

3 The supplied version of the header <cstdlib> shall declare at least the functions abort, atexit, at_quick_exit, exit, and quick_exit (18.5). [snip]

Table 16 个列表 ciso646, cstddef, cfloat, limits, climits, cstdint, cstdlib, new, typeinfo, exception, initializer_list, cstdalign, cstdarg, cstdbool, type_traits, 和 atomic.

上面的大多数 headers 包含类型、常量和模板的简单定义。唯一看起来有问题的是 typeinfoexceptioncstdlibnew。前两个分别支持 RTTI 和异常,您可以确保使用额外的编译器标志将其禁用。 cstdlibnew 您可以通过不调用 exit/at_exit 和不使用 new 表达式来简单地忽略。避免 at_exit 的原因是它可能会在内部调用 new。独立片段中的任何其他内容都不应调用 cstdlibnew.

中的任何内容

选项

上面最重要的选项是 -e _entry,它使您可以控制程序启动时运行的内容。

-ffreestanding 告诉编译器和标准库(而不是它的独立片段)不要假设整个标准库都存在(即使它仍然存在)。它可以防止生成令人惊讶的代码。请注意,此选项实际上并不限制 可以使用哪些 headers。例如,您仍然可以使用 iostream,尽管如果您还更改了入口点,这可能不是一个好主意。它的作用是阻止支持 freestanding headers 的代码调用 freestanding headers 之外的任何内容,并阻止编译器不会隐式生成任何此类调用。