使用 printf 的 %s 说明符打印 NULL 的行为是什么?

2024-10-09 14:08:00
admin
原创
227
摘要:问题描述:偶然发现一道有趣的面试题: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 库决定要做什么。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1120  
  IPD(Integrated Product Development,集成产品开发)流程是一种广泛应用于高科技和制造业的产品开发方法论。它通过跨职能团队的紧密协作,将产品开发周期缩短,同时提高产品质量和市场成功率。在IPD流程中,CDCP(Concept Decision Checkpoint,概念决策检查点)是一个关...
IPD培训课程   75  
  研发IPD(集成产品开发)流程作为一种系统化的产品开发方法,已经在许多行业中得到广泛应用。它不仅能够提升产品开发的效率和质量,还能够通过优化流程和资源分配,显著提高客户满意度。客户满意度是企业长期成功的关键因素之一,而IPD流程通过其独特的结构和机制,能够确保产品从概念到市场交付的每个环节都围绕客户需求展开。本文将深入...
IPD流程   66  
  IPD(Integrated Product Development,集成产品开发)流程是一种以跨职能团队协作为核心的产品开发方法,旨在通过优化资源分配、提高沟通效率以及减少返工,从而缩短项目周期并提升产品质量。随着企业对产品上市速度的要求越来越高,IPD流程的应用价值愈发凸显。通过整合产品开发过程中的各个环节,IPD...
IPD项目管理咨询   76  
  跨部门沟通是企业运营中不可或缺的一环,尤其在复杂的产品开发过程中,不同部门之间的协作效率直接影响项目的成败。集成产品开发(IPD)作为一种系统化的项目管理方法,旨在通过优化流程和增强团队协作来提升产品开发的效率和质量。然而,跨部门沟通的复杂性往往成为IPD实施中的一大挑战。部门之间的目标差异、信息不对称以及沟通渠道不畅...
IPD是什么意思   70  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用