用%p打印空指针是未定义的行为?

使用%p转换说明符打印空指针是未定义的行为吗?

 #include <stdio.h> int main(void) { void *p = NULL; printf("%p", p); return 0; } 

这个问题适用于C标准,而不适用于C实现。

这是我们受到英语语言限制和标准结构不一致的奇怪angular落案例之一。 所以充其量,我可以提出一个引人注目的反驳,因为它是不可能的certificate它:) 1


问题中的代码展现出明确的行为。

由于[7.1.4]是问题的基础,我们从这里开始:

除非在下面的详细描述中另有明确说明,否则以下每个声明均适用:如果函数的参数具有无效值( 例如函数域外的值或程序地址空间外的指针, 或空指针[…其他例子…][…]行为是未定义的。 […其他声明…]

这是笨拙的语言。 一种解释是列表中的项目对于所有库函数都是UB,除非被各个描述覆盖。 但是名单从“如”开始,表明它是说明性的,而不是详尽的。 例如,它没有提到string的正确空终止(对于例如strcpy的行为至关重要)。

因此,7.1.4的意图/范围很明显就是“无效价值”导致UB( 除非另有说明 )。 我们必须看每个函数的描述来确定什么是“无效值”。

例1 – strcpy

[7.21.2.3]只说:

strcpy函数将s2指向的string(包括终止空字符)复制到s1指向的数组中。 如果在重叠的对象之间进行复制,则行为是不确定的。

它没有明确提到空指针,但它也没有提到空终止符。 相反,从“ s2指向的string”推断出唯一有效的值是string(即指向以空字符结尾的字符数组为指针)。

事实上,这个模式可以在整个个人的描述中看到。 其他一些例子:

  • [7.6.4.1(fenv)]将当前的浮点环境存储​​在envp指向的对象中

  • [7.12.6.4(frexp)]将整数存储在exp 指向的int 对象中

  • [7.19.5.1(fclose)] stream指向stream

例2 – printf

[7.19.6.1]说这个关于%p

p – 参数应该是指向void的指针。 指针的值将以实现定义的方式转换为打印字符序列。

Null是一个有效的指针值,本节没有明确提及null是一个特殊情况,也不是指针必须指向一个对象。 因此它被定义为行为。


1.除非标准作者出面,否则除非我们能find类似于阐明事物的基本原理的文件。

简答题

是的 。 使用%p转换说明符打印空指针具有未定义的行为。 话虽如此,我不知道任何现有的符合实施将会行事不端。

答案适用于任何C标准(C89 / C99 / C11)。


长的答案

%p转换说明符需要指向voidtypes的参数,指针转换为可打印字符的转换是实现定义的。 它没有声明一个空指针是预期的。

对标准库函数的介绍指出,作为(标准库)函数参数的空指针被认为是无效值,除非另有明确说明。

C99 / §7.1.4 p1

如果某个函数的参数具有无效值(例如空指针,则行为未定义)。

预期空指针作为有效参数的(标准库)函数的示例:

  • fflush()使用空指针来清除“所有stream”(适用)。
  • freopen()使用空指针指示文件“当前与stream关联”。
  • 当n为零时, snprintf()允许传递一个空指针。
  • realloc()使用空指针来分配一个新的对象。
  • free()允许传递一个空指针。
  • strtok()为后续调用使用空指针。

如果我们考虑snprintf() ,那么在'n'为零时允许传递一个空指针是有意义的,但是对于允许类似的零'n'的其他(标准库)函数,情况并不是这样。 例如: memcpy()memmove()strncpy()memset()memcmp()

它不仅在标准库的介绍中有所规定,而且在这些function的介绍中又一次:

C99 §7.21.1 p2 / C11 §7.24.1 p2

如果声明为size_t n的参数指定函数的数组长度,那么在调用该函数时,n的值可以为零。 除非在本小节的特定function描述中另有明确规定,否则这种调用的指针参数仍应具有7.1.4所述的有效值。


这是故意的吗?

我不知道具有空指针的%p的UB是否实际上是有意的,但是因为标准明确指出空指针被认为是无效值作为标准库函数的参数,所以它明确指定了哪些情况一个空指针是一个有效的参数(snprintf,free等),然后它再次重复要求参数是有效的,即使在零“n”的情况下( memcpymemmovememset ),那么我认为它是有理由认为C标准委员会不太关心有这样的事情不明确。

C标准的作者没有详尽地列出实现必须满足的所有行为要求,以适合任何特定的目的。 相反,他们希望编写编译器的人不pipe标准是否要求,都会运用一定的常识。

UB是不是有用的问题。 真正重要的问题是:

  1. 试图编写高质量编译器的人是否应该以可预测的方式行事? 对于描述的情况,答案显然是肯定的。

  2. 程序员是否有权期望类似普通平台的质量编译器能以可预见的方式行事? 在描述的情况下,我会说答案是肯定的。

  3. 可能有些笨拙的编译器作者将这个标准的解释拉长,以certificate做一些奇怪的事情是合理的吗? 我希望不会,但不会排除。

  4. 消毒编译器是否应该对这种行为作出反应? 这将取决于他们用户的偏执狂水平; 一个消毒编译器可能不应该默认关于这种行为,但也许提供一个configuration选项,以防程序可能被移植到奇怪的“聪明”/哑编译器。

如果对标准的合理解释意味着一个行为被定义了,但是一些编译器作者将这个解释扩展到了正当的理由,那么这个标准说的是什么呢?