printf可以自动替换为C程序吗?
#include int puts(const char* str) { return printf("Hiya!\n"); } int main() { printf("Hello world.\n"); return 0; }
此代码输出“Hiya!” 什么时候跑。 有人可以解释原因吗?
编译行是: gcc main.c
编辑:它现在是纯C,任何无关的东西都已从编译行中删除。
是的,编译器可以通过对puts
的等效调用来替换对printf
的调用。
因为您定义了自己的函数puts
,其名称与标准库函数相同,所以程序的行为是未定义的。
参考: N1570 7.1.3:
在以下任何子条款[包括
puts
]中具有外部链接的所有标识符始终保留用作具有外部链接的标识符。
...
如果程序在保留它的上下文中声明或定义标识符(除了7.1.4允许的标识符),或者将保留标识符定义为宏名称,则行为是未定义的。
如果删除自己的puts
函数并检查汇编列表,则可能会在生成的代码中找到调用printf
,在源代码中调用printf
。 (我见过gcc执行这个特殊的优化。)
这取决于编译器和优化级别。 最新版本的GCC ,在一些常见系统上,经过一些优化,能够进行这样的优化(用put替换一个简单的printf
,其中AFAIU是合法的,如C99标准)
您应该在编译时启用警告(例如,首先尝试使用gcc -Wall -g
编译,然后使用gdb
调试,然后当您对代码有信心时使用gcc -Wall -O2
编译它)
顺便说一句,重新定义puts
真的很难看,除非你故意这样做(即编写自己的C库,然后你必须服从标准)。 您正在获得一些未定义的行为 (另请参阅此答案,了解UB的可能后果)。 实际上你应该避免重新定义标准中提到的名称,除非你真的非常清楚你在做什么以及编译器内发生了什么。
此外,如果您使用静态链接编译,如gcc -Wall -static -O main.c -o yourprog
我敢打赌链接器会抱怨(关于puts
多个定义)。
但IMNSHO你的代码是完全错误的,你知道的。
此外,您可以编译以获取汇编程序,例如使用gcc -fverbose-asm -O -S
; 你甚至可以要求gcc
溢出很多 “转储”文件,使用gcc -fdump-tree-all -O
可以帮助你理解gcc
正在做什么。
同样,这个特殊的优化是有效的 ,非常有用 :任何libc的printf
例程都必须在运行时 “解释”打印格式字符串(特别是处理%s
等…); 这实际上很慢。 一个好的编译器正确地避免在可能的情况下调用printf
(并替换为puts
)。
BTW gcc
不是唯一进行优化的编译器。 clang
也做到了。
另外,如果您使用编译
gcc -ffreestanding -O2 almo.c -o almo
almo
程序显示Hello world.
如果你想要另一种花哨和惊人的优化,请尝试编译
// file bas.c #include int f (int x, int y) { int r; int* p = malloc(2*sizeof(int)); p[0] = x; p[1] = y; r = p[0]+p[1]; free (p); return r; }
用gcc -O2 -fverbose-asm -S bas.c
然后查看bas.s
; 你不会看到对malloc
任何调用或free
(实际上,没有发出call
机器指令),再次, gcc
是正确的优化( clang
也是如此)!
PS:Gnu / Linux / Debian / Sid / x86-64; gcc
是版本4.9.1, clang
是版本3.4.2
尝试ltrace
你的可执行文件。 您将看到printf
被编译器的puts
调用替换。 这取决于你调用printf
的方式
这里有一个有趣的解读
据推测,你的库的printf()调用puts()。
您的puts()正在替换库版本。