c-为什么编译时已知的格式字符串没有优化



如今的编译器优化疯狂的东西。尤其是gcc和clang有时会进行非常疯狂的转换。

所以,我想知道为什么以下代码没有优化:

#include <stdio.h>
#include <string.h>
int main() {
printf("%dn", 0);
}

我希望一个强大的优化编译器能够生成与以下代码等效的代码:

#include <stdio.h>
#include <string.h>
int main() {
printf("0n");
}

但是gcc和clang在编译时不应用该格式(请参阅https://godbolt.org/z/Taa44c1n7)。为什么不应用这样的优化?

我经常看到一些格式参数在编译时是已知的,所以我想这可能是有意义的(尤其是对于浮点值,因为那里的格式可能相对昂贵)。

假设在一个函数的不同位置有以下四行:

printf("%dn", 100);
printf("%dn", 120);
printf("%dn", 157);
printf("%dn", 192);

当为典型的ARM微控制器进行编译时,这些printf调用中的每一个都将占用八个字节,并且所有四个函数调用将共享四个字节,用于保存字符串地址的副本,以及四个字节用于字符串本身。因此,代码和数据的总数将是40个字节。

如果用代替

printf("100n");
printf("120n");
printf("157n");
printf("192n");

那么,每个printf调用只需要六个字节的代码,但需要在内存中有自己单独的五个字节的字符串,以及四个字节来保存地址。因此,总的代码加数据需求将从40字节增加到60字节。远非优化,因此实际上会将代码空间需求增加50%。

即使只有一个printf调用,节省的费用也微乎其微。原始版本调用的8个字节加上8个字节的数据开销将是16个字节。第二个版本的代码需要8个字节的代码加上7个字节的数据,总共17个字节。在最好的情况下可以节省一点存储空间,在几乎不可信的情况下会花费大量成本的优化并不是真正的优化。

为什么编译时已知格式字符串没有优化?

编译器/编译器开发人员很容易支持一些潜在的优化,并且对编译器生成的代码的质量/性能有很大影响;一些潜在的优化对于编译器/编译器开发人员来说极难支持,并且对编译器生成的代码的质量/性能影响很小。显然,编译器开发人员将把大部分时间花在";更容易具有更高的冲击力";优化,以及一些";更硬,冲击更小";优化将被推迟(可能永远不会实现)。

printf("%dn", 0);优化为puts()(或者更好的fputs())看起来相对容易实现(但对性能的影响非常小,部分原因是这种情况在源代码中很少发生)。

真正的问题是;"滑坡">

如果编译器开发人员完成了编译器优化printf("%dn", 0);所需的工作,那么优化printf("%dn", x);呢?如果您优化了这些,那么为什么不同时优化printf("%04dn", 0);printf("%04dn", x);、浮点以及更复杂的格式字符串呢?printf("Hello %04dn Foo is %0.6f!n", x, y);肯定可以分解成一系列较小的函数(puts()atoi(),..)?

这个";"滑坡";意味着复杂性快速增加(因为编译器支持更多的格式字符串排列),而性能影响几乎没有改善。

如果编译器开发人员将把他们的大部分时间花在";更容易具有更高的冲击力";优化,他们不会热衷于进一步爬上那个特别滑的斜坡。

编译器怎么知道要链接什么类型的printf()函数?

这个函数有一个标准的含义,但没有人强迫您使用标准库
因此,提前完成函数应该做的事情超出了编译器的能力范围。

我的猜测是,printf()如何从内部C格式转换为用户表示是操作系统的问题,其区域设置约定(通常/总是可配置的;整数0";应该使用ascii-char48(我知道是一个极端的例子)。特别是,这里我可能错了,printf("n")在unix上输出换行符,在其他系统上输出CR+LF。我不确定这个转换是在哪里完成的,老实说,我认为这是一个小故障:我不止一次看到程序输出了错误的行终止,我认为是因为C编译器不应该在程序中编码行终止字符。这与假设一个特定的数字必须以特定的方式书写是一样的。

小补充:在这个问题下面有一条评论说,可以在运行时更改格式说明符的行为。如果这是真的,那么我们就有了这个问题的最终答案。。。有人能确认吗?

最新更新