#include <stdio.h>
int puts(const char* str)
{
return printf("Hiya!\n");
}
int main()
{
printf("Hello world.\n");
return 0;
}
此代码输出&#34; Hiya!&#34;什么时候跑。有人能解释一下原因吗?
编译行是:
gcc main.c
答案 0 :(得分:13)
是的,编译器可以通过对printf
的等效调用替换对puts
的调用。
因为您使用与标准库函数相同的名称定义了自己的函数puts
,所以您的程序行为未定义。
参考:N1570 7.1.3:
以下任何子条款[包括
puts
]中包含外部链接的所有标识符始终保留用作具有外部链接的标识符。
...
如果程序声明或定义了一个标识符 保留的上下文(7.1.4允许的除外),或定义保留的上下文 标识符作为宏名称,行为未定义。
如果您删除自己的puts
功能并检查汇编列表,则可能在生成的代码中找到puts
的来电,您调用了printf
在源代码中。 (我已经看到gcc执行了这个特殊的优化。)
答案 1 :(得分:8)
这取决于编译器和优化级别。最新版本的GCC,在某些常见系统上进行了一些优化,可以进行此类优化(将printf
替换为puts
,其中AFAIU是合法的,如C99标准)
您应该在编译时启用警告(例如,首先尝试使用gcc -Wall -g
进行编译,然后使用gdb
进行调试,然后当您对代码有信心时使用gcc -Wall -O2
进行编译)
puts
真的很难看,除非你故意这样做(即编写你自己的C库,然后你必须服从标准)。你得到了一些undefined behavior(另见this answer关于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 <stdlib.h>
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
答案 2 :(得分:1)
在您的可执行文件上尝试ltrace
。您将看到printf
被编译器的puts
调用替换。这取决于您调用printf
有趣的解读是here
答案 3 :(得分:0)
据推测,你的图书馆的printf()调用了puts()。
您的puts()正在替换库版本。