使用 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:...

问题描述:

偶然发现一道有趣的面试题:

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)printfprintf`

解决方案 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 库决定要做什么。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用