如何减小可执行文件的大小?

How to reduce the size of the executable?

当我使用 {fmt} 库编译此代码时,executable 大小变为 255 KiB,而仅使用 iostream header 则变为 65 KiB(使用GCC v11.2).

time_measure.cpp

#include <iostream>
#include "core.h"
#include <string_view>

int main( )
{
    // std::cout << std::string_view( "Oh hi!" );
    fmt::print( "{}", std::string_view( "Oh hi!" ) );

    return 0;
}

这是我的构建命令:

g++ -std=c++20 -Wall -O3 -DNDEBUG time_measure.cpp -I include format.cc -o runtime_measure.exe

iostream 相比,{fmt} 库不应该是轻量级的吗?或者我做错了什么?

编辑:通过在命令中添加 -s 以从 executable 中删除所有符号 table 和重定位信息,它变为 156 KiB。但仍然比 iostream 版本多 2.5 倍。

您忘记了 iostreams 已经包含在 stdlibc++ 中。因此它不计入二进制大小,因为它是一个共享库(通常)。我相信默认情况下 fmt 是作为静态库文件构建的,因此它会增加二进制文件的大小。您需要使用 -DBUILD_SHARED_LIBS=TRUE 将 fmt 编译为共享库,如 the building instructions

中所述

与任何其他图书馆一样,有固定成本和每次调用成本。 {fmt} 库的固定成本确实是 around 100-150k,没有调试信息(这取决于编译器标志)。在您的示例中,您正在比较与库链接的固定成本,而 iostreams 看起来更小的原因是因为它包含在动态链接的标准库本身中,不计入可执行文件的二进制大小。

请注意,此大小的很大一部分来自浮点格式化功能,它甚至不存在于 iostreams(最短往返表示)中。

如果您想比较每次调用的二进制大小,这对于具有大量格式化函数调用的实际代码更为重要,您可以查看目标文件或生成的程序集。例如:

#include <fmt/core.h>

int main() {
  fmt::print("Oh hi!");
}

生成 (https://godbolt.org/z/qWTKEMqoG)

.LC0:
        .string "Oh hi!"
main:
        sub     rsp, 24
        pxor    xmm0, xmm0
        xor     edx, edx
        mov     edi, OFFSET FLAT:.LC0
        mov     rcx, rsp
        mov     esi, 6
        movaps  XMMWORD PTR [rsp], xmm0
        call    fmt::v8::vprint(fmt::v8::basic_string_view<char>, fmt::v8::basic_format_args<fmt::v8::basic_format_context<fmt::v8::appender, char> >)
        xor     eax, eax
        add     rsp, 24
        ret

#include <iostream>

int main() {
  std::cout << "Oh hi!";
}

生成 (https://godbolt.org/z/frarWvzhP)

.LC0:
        .string "Oh hi!"
main:
        sub     rsp, 8
        mov     edx, 6
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        xor     eax, eax
        add     rsp, 8
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

除了 cout 的静态初始化外,没有太大区别,因为这里几乎没有格式化,所以在这两种情况下都只是一个函数调用。添加格式后,您将很快看到 {fmt} 的好处,请参阅例如http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r10.html#BinaryCode.

在您的 build/link 命令中,您为什么不使用 -Os 选项(优化大小)?