使用 printf 的 %s 说明符打印 NULL 的行为是什么?
- 2024-10-09 14:08:00
- admin 原创
- 84
问题描述:
偶然发现一道有趣的面试题:
test 1:
printf("test %s
", NULL);
printf("test %s
", NULL);
prints:
test (null)
test (null)
test 2:
printf("%s
", NULL);
printf("%s
", NULL);
prints
Segmentation fault (core dumped)
虽然这在某些系统上运行良好,但至少我的系统会抛出分段错误。这种行为的最佳解释是什么?上面的代码是用 C 编写的。
以下是我的 gcc 信息:
deep@deep:~$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
解决方案 1:
首先要说的是:printf
它的 %s 参数需要一个有效(即非 NULL)指针,因此向其传递 NULL 是正式未定义的。它可能会打印“(null)”,也可能会删除硬盘上的所有文件——就 ANSI 而言,这两种行为都是正确的(至少,Harbison 和 Steele 是这么告诉我的)。
话虽如此,这确实是很奇怪的行为。事实证明,当你执行如下简单操作时,会发生以下情况printf
:
printf("%s
", NULL);
gcc 非常聪明,可以将其解构为对的调用puts
。第一个printf
是这样的:
printf("test %s
", NULL);
非常复杂,因此 gcc 将改为发出对 real 的调用printf
。
(请注意,gcc 在编译时会发出有关无效printf
参数的警告。这是因为它很久以前就开发了解析格式字符串的能力*printf
。)
您可以通过使用该-save-temps
选项进行编译,然后查看生成的.s
文件来亲自查看这一点。
当我编译第一个例子时,我得到:
movl $.LC0, %eax
movl $0, %esi
movq %rax, %rdi
movl $0, %eax
call printf ; <-- Actually calls printf!
(评论是我添加的。)
但第二个产生了这个代码:
movl $0, %edi ; Stores NULL in the puts argument list
call puts ; Calls puts
请注意,这种优化是正确的,即它对有效字符串产生相同的结果;尤其是puts
在字符串后打印换行符。
解决方案 2:
就 C 语言而言,原因在于您正在调用未定义的行为,并且任何事情都可能发生。
至于为什么会发生这种情况的机制,现代 gcc 优化`printf("%s
", x)为
puts(x),并且在看到空指针时
puts没有打印愚蠢的代码,而常见的实现有这种特殊情况。由于 gcc 无法(通常)优化这样的非平凡格式字符串,因此实际上当格式字符串中包含其他文本时会被调用。
(null)printf
printf`
解决方案 3:
(C99 或 C11)第 7.1.4 节规定:
§7.1.4 库函数的使用
¶1 除非在随后的详细描述中另有明确说明,否则以下每个语句均适用:如果函数的参数具有无效值(例如,函数域之外的值,或程序地址空间之外的指针,或空指针,或当相应参数不是 const 限定时指向不可修改存储的指针)或类型(提升后)不是具有可变数量参数的函数所期望的,则行为未定义。
由于规范printf()
没有说明当您将空指针传递给说明符时会发生什么%s
,因此行为明确未定义。(请注意,传递空指针以供说明符打印%p
不是未定义的行为。)
这是家庭行为的“章节和诗句” fprintf()
(C2011 — 在 C1999 中它是不同的章节编号):
§7.21.6.1 fprintf 函数
s
如果没有l
长度修饰符,则参数应是指向字符类型数组的初始元素的指针。[...]如果
l
存在长度修饰符,则参数应是指向 wchar_t 类型数组的初始元素的指针。
p
参数应为指向 void 的指针。指针的值将以实现定义的方式转换为打印字符序列。
转换说明符的规范s
排除了空指针有效的可能性,因为空指针不指向相应类型数组的初始元素。p
转换说明符的规范不要求 void 指针指向任何特定内容,因此 NULL 有效。
许多实现(null)
在传递空指针时会打印字符串,但依赖这种善意是危险的。未定义行为的美妙之处在于,这种响应是允许的,但不是必需的。同样,崩溃是允许的,但不是必需的(更可惜的是——如果人们在一个宽容的系统上工作,然后移植到其他不那么宽容的系统上,就会受到伤害)。
解决方案 4:
指针NULL
不指向任何地址,尝试打印它会导致未定义的行为。未定义意味着当编译器或 C 库尝试打印 NULL 时,由编译器或 C 库决定要做什么。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件