为什么 printf 在使用 MinGW-w64 时会生成一个额外的函数
Why does printf generate an extra function when using MinGW-w64?
查看 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_vprintf
是printf
调用的内部函数 - 不需要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 在这种特定情况下无法内联。如果内联失败,编译器必须创建一个显式函数体。没有间接寻址的常规行为可以通过成功的内联来解释。
这里有三件事要实现,没有一个答案包含所有三件事(至少目前是这样(。
-
_Z6printfPKcz
是printf
的残缺名字。请看伊米比斯的回答。 -
printf
函数有多种变体,只有一个或只有几个函数实现该功能。后一个函数,其名称以__
开头,完成繁重的工作,并且是实现的内部;向用户公开的函数不做任何重要的工作,它们基本上只是将调用转发到另一个转发函数或实现函数。参见Mats Petersson的回答。 -
在您的情况下,转发功能的内联失败。参见巴西列夫的回答。
根据您的评论,我怀疑您的真正问题是为什么内联失败,为什么不直接调用实现,没有任何开销。好问题,我没有看到任何有效的技术原因。
我知道 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 会比额外的函数调用花费你更多的时间。
- 创建一个函数以在输入为负数或零时输出字符串.第一次执行用户定义的函数
- 如何仅为一个函数添加延迟
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 我需要将多个函数组合为一个函数
- 在C++中声明一个函数时,它需要有函数本身的参数吗
- 如何创建一个函数来计算并返回平均值、最大值和最小值
- 一个函数,用于查找字符串1包含字符串2 c++的次数
- 如何将一个类的函数作为另一个类的另一个函数的参数传递
- 编写一个函数以使用 n 百分比的 CPU 使用率
- 将 N-arg 函数包装到另一个函数中
- 如何封装一个函数,以便它只能由同一类中的一个其他函数调用?
- C++(.cpp文件和.h文件)拆分代码并添加一个函数,提取 - 这很容易吗?
- C++从另一个函数退出函数
- 编写一个函数来删除单链表中的节点(尾部除外),仅授予对该节点的访问权限
- 视觉我希望一个函数在另一个函数C++中进行计算
- C ++如何在原始抽象类中创建一个函数,该函数接受派生类的输入
- 在另一个函数 (c++) 中调用变量
- 如何在另一个函数中使用返回值作为参数?
- 如何包装一个函数以适应另一个函数的所需类型
- 创建一个函数的 Python 绑定,返回指向带有 boost 的向量的指针