是否必须将未定义的行为定义为未定义?

时间:2017-11-22 12:10:18

标签: c undefined-behavior

尽管出现了标题,但是一个哲学问题。

然而,它严格地讲述语义。

背景

在先前的编辑中,此answer表示读取未初始化的值是未定义的行为。我同意这个评估。其他人不是。

经过一番挖掘后,我得出的结论是,它是否是未定义的行为是未定义的。

标准6.3.2.1中的第2小节仅规定访问未初始化的对象

  

[...]可以用寄存器存储类[...]

声明

是未定义的行为。

其他部分,例如6.7.9 [Initializers]或6.5.2.1 [Array subscripting],未提及访问未初始化的值。

所以毫无疑问是否有人提到这个特殊情况被标准称为未定义行为。

语义

但是:3.4.3 1指出未定义的行为

  

行为,在使用不可移植或错误的程序结构或错误数据时,       本国际标准没有要求

从未初始化的数组中读取

  • 使用错误数据
  • 使用非便携式构造。 (即内存分配的具体细节 1
  • 导致行为,
  • 标准没有要求产生可预测的效果

我称之为“未定义的行为”。 但也许我错过了一些东西(?)

更广泛的问题仍然存在:

是否必须明确提及行为,被视为“未定义的行为”?

注意:

  • 这个问题特定于C语言及其社区
  • 问题是关于语义。它并不质疑从未初始化的数组中读取是否“合理”。 (除非你是笔测试员,否则不是这样)
  • 我使用了this C11标准草案

脚注:

  1. malloc calloc等等,确实对如何执行分配有要求。但声明固定长度数组并未指定未初始化数据的性质。

3 个答案:

答案 0 :(得分:1)

当一个对象未​​初始化时,它具有不确定的 * 值。仅当此值是陷阱表示时,访问此对象才会调用未定义的行为 这说明你所指的答案有错误的推理。 Accepted answer是正确的。

* 不确定值
要么是未指定的值,要么是陷阱表示
未指定的值:
相关类型的有效值,其中本国际标准未规定在任何情况下选择哪个值的要求
注意未指定的值不能是陷阱表示 陷阱表示:
一个对象表示,不需要表示对象类型的值
(§3.19.2,§3.19.3,§3.19.4)

答案 1 :(得分:1)

不,未定义时,行为未定义,禁止“不得”或明确标记为未定义。以下是C标准第4条“一致性”第2段中对这些内容的定义:

  

如果“必须”或“不应”要求出现在a。之外   违反了约束或运行时约束,行为是   未定义。此处另有说明未定义的行为   国际标准中的“未定义行为”或由   省略任何明确的行为定义。没有   这三者之间的重点不同;他们都描述了“行为   这是未定义的“。

答案 2 :(得分:1)

C89标准主要是从描述性而非规范性的角度编写的,当时很多行为都会有一些但不是所有实现都指定了行为,这在很大程度上取决于目标平台和应用领域。此外,许多行动在某些情况下会产生可预测且易于指定的后果,而在其他情况下则不然。标准的作者没有尽力详尽地描述当质量实现应该预期可行的情况时的所有情况组合,甚至在其基本原理文档中指出,足够低质量的实现可能同时符合和无用

参考从数组中读取未初始化的数据,请考虑以下代码:

float test(int a, int b, int c, int d)
{
  float result;
  float *f = malloc(10*sizeof float);
  f[a] = 1.0f; f[b] = 2.0f; f[c] = 3.0f;
  result = f[d];
  free(f);
  return result;
}

在某些平台上,尝试将某些位模式处理为浮点数可能会触发可能未配置的陷阱处理程序。我认为非常清楚标准的作者希望避免要求这些平台的实现阻止上述代码如果例如触发陷阱处理程序。代码调用`test(1,2,3,4),也不要求它们限制这种陷阱的后果。此外,我不认为他们会在这样的平台上看到负面的质量实现,这些代码可能产生任意和不可预测的影响。

但是,假设代码是:

typedef struct { float v; } flt;

flt test2(int a, int b, int c, int d)
{
  flt result;
  flt *f = malloc(10*sizeof flt);
  f[a].v = 1.0f; f[b].v = 2.0f; f[c].v = 3.0f;
  result = f[d];
  free(f);
  return result;
}

标准明确禁止结构具有陷阱表示。因此,f[d]无法保存陷阱表示,因此在读取时没有任何理由会发生任何奇怪的事情。如果调用test2的代码尝试使用结果的字段v,则 可能会触发不需要的陷阱处理程序,但如果没有任何内容对该字段执行任何操作,则应该没问题。

不幸的是,由于标准的作者没有试图列出所有应该预期质量实施可预测的情况组合,他们认为没有必要区分某些行为可能具有的情况。在某些情况下不可预测的行为,即那些行动应该被视为从未产生可预测后果的行为,即使在标准的其他部分或实施文件可以保证行为的情况下也是如此。

C89的作者表示,他们希望尽可能避免破坏现有代码,但他们未能在程序依赖它们的许多情况组合中明确指定行为。除非他们说谎或无能,否则这种失败在很大程度上归因于一种信念,即实施者不应该需要所有此类行为的完整列表,以便在需要时识别和支持它们。

如果能够相信配置为适合低级编程的质量实现的编译器,其目标是没有陷阱表示的平台,则不会完全跳过读取未初始化的数组值时是否有轨道?鉴于支持这种保证的成本很低,我建议任何不能支持它的实现都不是适合低级编程的高质量编译器。

如果给定的代码依赖于标准未强制执行的行为,或者甚至做了由标准定义但在没有优化的情况下会相同的情况,那么gcc和clang是否会表现得很奇怪?对那些不是吗?不是。如果某些编译器有效地处理行为而其他人试图避免这样做并且查看标准试图将行为明确定义为标准中的缺陷的少数情况,那么标准是否定义了特定行为并不重要。