为什么 printf 在使用 MinGW-w64 时会生成一个额外的函数

Why does printf generate an extra function when using MinGW-w64?

本文关键字:一个 函数 printf MinGW-w64 为什么      更新时间:2023-10-16

查看 GCC 使用 -O2 输出的程序集,我发现如果我使用 printf GCC 将创建一个名为 _Z6printfPKcz 的函数,然后调用 __mingw_vprintf 。这种间接的目的是什么?为什么printf不直接转换为__mingw_printf电话?对于其他相关函数(如 sprintf(也是如此。

编辑:

作为记录,我知道什么是名称重整以及它是如何工作的。我的问题是,为什么编译器首先生成函数,该函数除了将参数转发给__mingw_vprintf之外什么都不做,而它可以简单地直接调用__mingw_printf并保存不必要的间接寻址。

换句话说,为什么会这样:

printf("%dn", var);

编译成这样:

_Z6printfPKcz:
    sub rsp, 56
    mov QWORD PTR 72[rsp], rdx
    lea rdx, 72[rsp]
    mov QWORD PTR 80[rsp], r8
    mov QWORD PTR 88[rsp], r9
    mov QWORD PTR 40[rsp], rdx
    call    __mingw_vprintf
    add rsp, 56
    ret
main:
    ...
    call    _Z6printfPKcz
    ...

当这足够

main:
    ...
    call    __mingw_printf
    ...

_Z6printfPKcz只是printf的损坏名称 - 搜索"名称重整"以获取更多信息。这是实际的 printf 函数。

__mingw_vprintfprintf调用的内部函数 - 不需要printf不调用另一个函数来完成它的工作。也许__mingw_vprintf处理所有的格式和打印,printf是这样写的:

int printf(const char *fmt, ...)
{
    va_list va;
    va_start(va, fmt);
    int result = __mingw_vprintf(fmt, va);
    va_end(va);
    return result;
}

_pZ6printfPKcz是具有签名printf(char const*, ...)的函数的C++损坏的名称 - 换句话说,实际的printf函数。这似乎是通过调用另一个函数来实现的。由于printf有六种不同的变体(具有不同类型的输出,例如输出到文件、输出到字符串、输出到控制台等(,因此拥有一个"do printf"函数是有意义的 - 在这种情况下似乎是__mingq_vprintf .由于 printf 实现是几千行代码,我们真的不想为所有不同的变体复制它 6 次左右(我知道,因为在工作中我一直在为 OpenCL 实现一个版本的 printf - 这是一个非常复杂的功能,你绝对不想让实际代码多次重复(。

您可以在"stdio.h"中看到printf的确切定义:

http://sourceforge.net/apps/trac/mingw-w64/browser/trunk/mingw-w64-headers/crt/stdio.h?rev=5437#L283

至于他们这样做的确切原因,这确实是你必须问 mingw 的开发人员的事情 - 但我的假设是他们试图减少不同变体的数量 - 例如__mingw_vprintf也用于实现实际的 vprintf 变体,所以这是"已经一半的变体"。

额外的分层还允许 printf 在窄字符 (ASCII( 和宽字符(所谓的 UNICODE(之间切换,如此处的宏所示:

http://sourceforge.net/apps/trac/mingw-w64/browser/trunk/mingw-w64-crt/stdio/mingw_pformat.h?rev=3972#L69

当你意识到__mingw_vprintf变成了另一层调用时,你会更加讨厌 mingw 开发人员:

http://sourceforge.net/apps/trac/mingw-w64/browser/trunk/mingw-w64-crt/stdio/mingw_vprintf.c?rev=2296#L51

但是,在printf实现的整个方案中,就像我在评论中所说的那样,开销非常小-_pformat代码有2000行长:

http://sourceforge.net/p/mingw/mingw-org-wsl/ci/21762bb4a1bd0c88c38eead03f59e8d994349e83/tree/misc/src/libcrt/stdio/pformat.c

(这比glibc实现小得多(

Printf 在这种特定情况下无法内联。如果内联失败,编译器必须创建一个显式函数体。没有间接寻址的常规行为可以通过成功的内联来解释。

这里有三件事要实现,没有一个答案包含所有三件事(至少目前是这样(。

  1. _Z6printfPKczprintf的残缺名字。请看伊米比斯的回答。

  2. printf函数有多种变体,只有一个或只有几个函数实现该功能。后一个函数,其名称以 __ 开头,完成繁重的工作,并且是实现的内部;向用户公开的函数不做任何重要的工作,它们基本上只是将调用转发到另一个转发函数或实现函数。参见Mats Petersson的回答。

  3. 在您的情况下,转发功能的内联失败。参见巴西列夫的回答。

根据您的评论,我怀疑您的真正问题是为什么内联失败,为什么不直接调用实现,没有任何开销。好问题,我没有看到任何有效的技术原因。

我知道 gcc(至少在 Linux 上(如果不使用链接时间优化,即使函数体(实现(和调用站点都在同一个翻译单元中(在同一个源文件中(,也不会内联函数。

尝试启用链接时间优化。请参阅控制优化的选项中的-flto调用很可能会内联链接时间优化;该函数似乎是内联的良好候选者。


更新:我无法测试mingw;这是我在Linux上的测试。此代码:

#include <stdio.h>
int main(int argc, char** argv) {
  printf("%dn", argc);
}

导致以下程序集禁用优化-O0

    [...]
    call    printf
    [...] 

也就是说,对printf的调用不是内联的。但是,我已经在-O1得到:

    [...]
    call    __printf_chk
    [...]

这意味着对printf的调用是内联的,我直接调用底层实现。它甚至不需要链接时间优化。你只需要激励编译器做内联。

考虑一下:编译器可能不会在编译时内联 C 运行时中的函数,因为它们应该在运行时访问。但它肯定已经在 -O1 内联了包装器函数。

至于为什么特定平台上的特定版本的编译器无法内联特定函数,嗯......如果您认为这是应用程序中的性能下降,请尝试提交错误报告。如果我是你,我不会太担心,由于内联失败,IO 会比额外的函数调用花费你更多的时间。