Linux 使用 printf 的 %s 说明符打印 NULL 的行为是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11589342/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the behavior of printing NULL with printf's %s specifier?
提问by Deepanjan Mazumdar
Came across an interesting interview question:
遇到一个有趣的面试问题:
test 1:
printf("test %s\n", NULL);
printf("test %s\n", NULL);
prints:
test (null)
test (null)
test 2:
printf("%s\n", NULL);
printf("%s\n", NULL);
prints
Segmentation fault (core dumped)
Though this might run fine on some systems, atleast mine is throwing a segmentation fault. What would be the best explanation of this behavior? Above code is in C.
尽管这可能在某些系统上运行良好,但至少我的会引发分段错误。这种行为的最佳解释是什么?上面的代码是 C 语言。
Following is my gcc info:
以下是我的 gcc 信息:
deep@deep:~$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
采纳答案by Chris Reuter
First things first: printf
is expecting a valid (i.e. non-NULL)
pointer for its %s argument so passing it a NULL is officially
undefined. It may print "(null)" or it may delete all files on your
hard drive--either is correct behavior as far as ANSI is concerned
(at least, that's what Harbison and Steele tells me.)
首先要做的事情printf
是:期望为其 %s 参数提供一个有效的(即非 NULL)指针,因此将 NULL 传递给它是正式未定义的。它可能会打印“(null)”,也可能会删除硬盘驱动器上的所有文件——就 ANSI 而言,这两种行为都是正确的(至少,Harbison 和 Steele 是这样告诉我的。)
That being said, yeah, this is really wierd behavior. It turns out
that what's happening is that when you do a simple printf
like this:
话虽如此,是的,这确实是一种奇怪的行为。事实证明,当你做这样一个简单的事情时,会发生什么printf
:
printf("%s\n", NULL);
gcc is (ahem) smart enough to deconstruct this into a call to
puts
. The first printf
, this:
gcc(咳咳)足够聪明,可以将其解构为对
puts
. 第一个printf
,这个:
printf("test %s\n", NULL);
is complicated enough that gcc will instead emit a call to real
printf
.
足够复杂以至于 gcc 会发出对 real 的调用
printf
。
(Notice that gcc emits warnings about your invalid printf
argument
when you compile. That's because it long ago developed the ability to
parse *printf
format strings.)
(请注意,gccprintf
在编译时会发出有关无效参数的警告。那是因为它很久以前就开发了解析*printf
格式字符串的能力。)
You can see this yourself by compiling with the -save-temps
option
and then looking through the resulting .s
file.
您可以通过使用该-save-temps
选项进行编译然后查看生成的.s
文件来自己查看这一点。
When I compiled the first example, I got:
当我编译第一个例子时,我得到:
movl $.LC0, %eax
movl movl ##代码##, %edi ; Stores NULL in the puts argument list
call puts ; Calls puts
, %esi
movq %rax, %rdi
movl ##代码##, %eax
call printf ; <-- Actually calls printf!
(Comments were added by me.)
(评论是我加的。)
But the second one produced this code:
但是第二个产生了这个代码:
##代码##The wierd thing is that it doesn't print the following newline. It's as though it's figured out that this is going to cause a segfault so it doesn't bother. (Which it has--it warned me when I compiled it.)
奇怪的是它不打印以下换行符。好像已经发现这会导致段错误,所以它不会打扰。(它有 - 它在我编译它时警告我。)
回答by Yunchi
The NULL
pointer doesn't point to any address, and attempting to print it causes undefined behavior. Undefined meaning it's up to your compiler or C library to decide what to do when it tries to print NULL.
该NULL
指针不指向任何地址,并试图打印它会导致不确定的行为。未定义意味着由您的编译器或 C 库决定在尝试打印 NULL 时要执行的操作。
回答by R.. GitHub STOP HELPING ICE
As far as the C language is concerned, the reason is that you're invoking undefined behavior and anything can happen.
就 C 语言而言,原因是您正在调用未定义的行为,任何事情都可能发生。
As for the mechanics of why this is happening, modern gcc optimizes printf("%s\n", x)
to puts(x)
, and puts
does not have the silly code to print (null)
when it sees a null pointer, whereas common implementations of printf
have this special case. Since gcc can't optimize (in general) non-trivial format strings like this, printf
actually gets called when the format string has other text present in it.
至于为什么会发生这种情况的机制,现代 gcc 优化printf("%s\n", x)
为puts(x)
,并且在看到空指针时不会puts
打印愚蠢的代码(null)
,而 的常见实现printf
具有这种特殊情况。由于 gcc 无法优化(通常)这样的非平凡格式字符串,因此printf
当格式字符串中存在其他文本时实际上会被调用。
回答by Jonathan Leffler
Section 7.1.4 (of C99 or C11) says:
第 7.1.4 节(C99 或 C11)说:
§7.1.4 Use of library functions
?1 Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow: If an argument to a function has an invalid value (such as a value outside the domain of the function, or a pointer outside the address space of the program, or a null pointer, or a pointer to non-modifiable storage when the corresponding parameter is not const-qualified) or a type (after promotion) not expected by a function with variable number of arguments, the behavior is undefined.
§7.1.4 库函数的使用
?1 除非在随后的详细说明中另有明确说明,否则以下每一条语句都适用:如果函数的参数具有无效值(例如函数域之外的值,或函数地址空间之外的指针)程序,或空指针,或指向不可修改存储的指针(当相应的参数不是 const 限定的)或类型(提升后)不是具有可变数量参数的函数所期望的,行为是未定义的。
Since the specification of printf()
says nothing about what happens when you pass a null pointer to it for the %s
specifier, the behaviour is explicitly undefined. (Note that passing a null pointer to be printed by the %p
specifier is not undefined behaviour.)
由于 的规范printf()
没有说明当您将空指针传递给%s
说明符时会发生什么,因此行为是明确未定义的。(请注意,传递要由说明%p
符打印的空指针不是未定义的行为。)
Here is the 'chapter and verse' for the fprintf()
family behaviour (C2011 — it is a different section number in C1999):
这是fprintf()
家庭行为的“章节和诗句” (C2011 - 它是 C1999 中的不同部分编号):
§7.21.6.1 The fprintf function
s
If nol
length modifier is present, the argument shall be a pointer to the initial element of an array of character type. [...]If an
l
length modifier is present, the argument shall be a pointer to the initial element of an array of wchar_t type.
p
The argument shall be a pointer to void. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.
§7.21.6.1 fprintf 函数
s
如果不存在l
长度修饰符,则参数应是指向字符类型数组的初始元素的指针。[...]如果存在
l
长度修饰符,则参数应是指向 wchar_t 类型数组的初始元素的指针。
p
参数应是指向 void 的指针。指针的值以实现定义的方式转换为打印字符序列。
The specifications for the s
conversion specifier preclude the possibility that a null pointer is valid since the null pointer does not point to initial element of an array of the appropriate type. The specification for the p
conversion specifier does not require the void pointer to point at anything in particular and NULL is therefore valid.
的详细规格s
说明符排除一个空指针是有效的,因为空指针不指向适当类型的阵列的初始元素的可能性的转换。p
转换说明符的规范不要求 void 指针特别指向任何东西,因此 NULL 是有效的。
The fact that many implementations print a string such as (null)
when passed a null pointer is a kindness that is dangerous to rely upon. The beauty of undefined behaviour is that such a response is permitted, but it is not required. Similarly, a crash is permitted, but not required (more's the pity – people get bitten if they work on a forgiving system and then port to other less forgiving systems).
许多实现打印字符串的事实,例如(null)
当传递一个空指针时,是一种危险的善意依赖。未定义行为的美妙之处在于这样的响应是允许的,但不是必需的。类似地,崩溃是允许的,但不是必需的(更遗憾的是——如果人们在一个宽容的系统上工作然后移植到其他宽容度较低的系统上,他们会被咬伤)。