为什么printf在使用MinGW-w64时会产生额外的功能?

时间:2014-05-10 00:39:45

标签: c++ c gcc mingw mingw-w64

通过-O2查看GCC的程序集输出我看到如果我使用printf,GCC将创建一个名为_Z6printfPKcz的函数,然后调用__mingw_vprintf。这个间接的目的是什么?为什么printf不会直接转换为对__mingw_printf的调用?其他相关功能也是如此,例如sprintf。

编辑:

为了记录,我知道修剪的名称是什么以及它是如何工作的。我的问题是为什么编译器首先生成函数,这个函数除了将参数转发给__mingw_vprintf之外什么都不做,只需直接调用__mingw_printf并保存不必要的间接。

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

printf("%d\n", 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
    ...

4 个答案:

答案 0 :(得分:3)

_Z6printfPKcz只是printf的错误名称 - 搜索“name mangling”以获取更多信息。这是实际的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;
}

答案 1 :(得分:2)

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

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

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

至于为什么他们这样做,你真的需要问开发人员mingw - 但我的假设是他们试图保持不同变体的数量 - 例如{{1 }}也用于实现实际的__mingw_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实现的整个方案中,就像我在评论中所说的那样,它的开销非常小 - vprintf代码长达2000行:

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

(比glibc实现小得多)

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

  1. _Z6printfPKczprintf的错位名称。请参阅immibis' answer

  2. printf函数有多种变体,一个或几个函数实现功能。后面的函数(名称以__开头)执行繁重,并且是实现的内部函数;暴露给用户的功能不会做任何重要的工作,它们基本上只是将呼叫转发到另一个转发功能或实现功能。请参阅Mats Petersson's answer

  3. 在您的情况下,转发功能的内联失败。请参阅Basilevs' answer

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

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

    尝试启用链接时优化。请参阅Options That Control Optimization中的-flto很可能会通过链接时间优化内联调用;这些功能似乎是内联的良好候选者。


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

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

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

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

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

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

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

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

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