向printf()添加换行符会改变代码行为

Stu*_*ent 23 c linux printf

出于某种原因,加入\nprintf()改变的下面的代码的行为.没有\n打印(null)的代码而\n导致的代码Segmentation fault.

Printf.c

#include <stdio.h>

int main(int argc, char* argv[]){
    printf("%s", argv[1]);
} 
Run Code Online (Sandbox Code Playgroud)

Printf.c - 输出

$ gcc -o Printf Printf.c
$ ./Printf
(null)
Run Code Online (Sandbox Code Playgroud)

Printf_Newline.c

#include <stdio.h>

int main(int argc, char* argv[]){
    printf("%s\n", argv[1]);
}
Run Code Online (Sandbox Code Playgroud)

Printf_Newline.c - 输出

$ gcc -o Printf_Newline Printf_Newline.c
$ ./Printf_Newline
Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)

我很想知道这背后的原因.

小智 55

两者都是未定义的行为,所以答案可能会在这里停止.

但至少有一个解释输出(null).这是glibc(GNU C库)的扩展.通过0%sprintf()被认为是不确定的 C标准,因此很可能导致崩溃.开发人员glibc决定做一些有意义的事情.

然而,第二次崩溃的原因是,使用换行符,编译器决定优化:而不是printf("%s\n", argv[1]),它执行puts(argv[1]),根据C标准在语义上是等效的,因此是允许的优化.但glibcs"(null)-trick"仅在printf().中实现.

程序中还有另一个未定义的行为:您可能访问argv 越界.有没有保证什么价值,你会发现在argv[i]的时候i > argc.有一点点机会argc可能是0,所以你也可以体验其他任何东西.

  • 只有当`i> argc`(严格地说更大,不是更大或相等)时,它才是未定义的行为 - `argv [argc]`保证是空指针.但我没有在该程序中看到任何"argc> = 1"的检查. (11认同)

chq*_*lie 11

如果程序没有给出任何命令行参数,代码在两种情况下都有未定义的行为,因此任何事情都可能发生.

既然你很好奇(对你有好处!),这里有一个你观察到的可能的解释:

  • printf("%s\n", argv[1]);可以由编译器优化成puts(argv[1]);,而printf("%s", argv[1]);仍然调用printf().

  • printf接受空指针作为%s转换参数的一些实现,因此输出(null).

  • puts() 具有未指定的空指针行为,在您的情况下是分段错误.

尝试编译两个没有任何优化(-O0),看看你是否得到(null)输出\n.

您可以使用godbolt的编译器资源管理器,看看如何clang使用-O0更改行为,但不能gcc.

  • @Beginner:它实际上取决于你的编译器,最近版本的`gcc`似乎优化了`printf`,即使被告知没有`-O0`,而`clang`确实保持对`printf()`的调用. (2认同)