glibc函数的GCC、-flto、-fno内置和自定义函数实现

GCC, -flto, -fno-builtin and custom function implementation of glibc functions

本文关键字:内置 自定义函数 实现 -fno -flto 函数 GCC glibc      更新时间:2023-10-16

我观察到GCC标志-fltojemalloc/tcmalloc的意外行为(至少我找不到解释)。一旦使用了-flto,并且我与上面的库malloc/caloc链接,并且朋友没有被je/tc malloc实现取代,就调用了glibc实现。一旦我删除了-flto标志,一切都按预期进行。我尝试将-fno-builtin/-fno-builtin-*-flto一起使用,但它仍然没有选择je/tc malloc实现。

-flto机械是如何工作的?为什么二进制文件不选择新的实现?当它在未解析的外部(例如printf)上失败时,它是如何与-fno-builtin链接的?

EDIT001:
GCC 7.3
样本代码

int main()
{
auto p = malloc(1024);
free(p);
return 0;
}

编译:

/usr/bin/c++-O2-g-DNDEBUG-flto-std=gnu++14-oCMakeFiles/flto.dir/main.cpp.o-c/主页/用户/开发/CPPUnk/flto/main.cpp

链接:

/usr/bin/c++-O2-g-DNDEBUG-flto-CMakeFiles/flto.dir/main.cpp.o-o flto-L/home/user/Development/jemalloc-Wl、-rpath、/home/user/Development/jemallac-ljemalloc

EDIT002:
更合适的样本代码

#include <cstdlib>
int main()
{
auto p = malloc(1024);
if (p) {
free(p);
}
auto p1 = new int;
if (p1) {
delete p1;
}
auto p2 = new int[32];
if (p2) {
delete[] p2;
}
return 0;
}

首先,您的示例代码是错误的。仔细阅读C11标准n1570。当您想要使用标准malloc时,您应该使用#include <stdlib.h>

在C++11中(读作n3337),malloc是不受欢迎的,不应该使用(更喜欢new)。如果你仍然想在C++中使用std::malloc,你应该使用#include <cstdlib>(在GCC中,它在内部包括<stdlib.h>)

那么您的示例代码几乎是C代码(一旦用void*替换auto),而不是C++。它可以优化(一旦包含<stdlib.h>),甚至在没有-flto但只有-O3的情况下,根据假设规则,也可以优化为空的main。(我甚至写了一份公开报告,bismon-chariot-doc.pdf,其中有一节§1.4.2,在几页中解释了优化是如何发生的)。

为了围绕mallocfree进行优化,GCC在malloc的声明(<stdlib.h>内部)中使用了一些__attribute__(malloc)函数属性。

-flto机器是如何工作的?

LTO在GCC内部§25中进行了解释

它通过在"编译"answers"链接"时使用代码的一些内部(类似于GIMPLE和/或SSA)表示来工作(实际上,链接步骤变成了另一个具有整个程序优化的编译,因此您的代码在实践中会被"编译"两次)。

LTO总是应该(在实践中)在编译和链接时与一些优化标志(例如-O2甚至-O3)一起使用。因此,您应该编译并用g++ -flto -O2链接(使用不带的-flto至少-O2是没有实际意义的,并且在编译和链接时应该使用完全相同的优化标志)。

更准确地说,-flto还将源代码的一些内部(类似于GIMPLE)表示嵌入到对象文件中,并且也在"链接时"使用(尤其是在"链接"整个程序时,再次使用其GIMPLE进行优化和内联)。事实上,GCC包含一些LTO前端和名为lto1的编译器(除了C++前端和称为cc1plus的编译器),而lto1(当您g++ -flto -O2链接时)在链接时用于重新处理这些GIMPLE表示。

libjemalloc可能有自己的头,并且可能有inline(或可内联)函数。然后,当从源代码编译该库时,您还需要使用-flto -O2(以便其Gimple存储在库中)

最后,通常的malloc被调用的事实是独立于-flto的。这是一个链接器问题,而不是编译器问题。您可以尝试静态链接-ljemalloc(然后最好也使用gcc -flto -O2构建该库;如果不这样构建,就无法在malloc调用中获得LTO优化)。

您还可以将-v传递给编译和链接命令,以了解g++的作用。您甚至可以通过-Wl,--verbose来要求ld(由g++启动)冗长。

请注意,LTO(以及它正在使用的内部表示)是编译器和版本特定的。内部(Gimple&SSA)表示在GCC7&GCC 8(在Clang中,它与非常不同,因此当然不兼容)。动态链接器ld-linux(8)不知道LTO。

PS。您可以安装libjemalloc-dev软件包并在代码中添加#include <jemalloc/jemalloc.h>。另请参阅jemalloc(3)手册页。可能CCD_ 57可以被配置或修补以定义某个CCD_ 58符号作为CCD_ 59的替换。那么(对于LTO)在代码中使用je_malloc会更简单(以避免几个mallocELF符号之间的冲突)。要了解有关共享库中符号的更多信息,请阅读Drepper的如何编写共享库论文。当然,你应该期待LTO改变链接的行为!