【问题标题】:Does undefined behaviour have to be defined to be undefined?未定义的行为是否必须定义为未定义?
【发布时间】:2017-11-22 12:10:17
【问题描述】:

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

然而,它是严格意义上的语义。

背景

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.4.3.1 回答了更广泛的问题。如果某些行为不是由标准明确或隐含地强加的,那就是 UB。
  • 真正的哲学问题是:C 的真正实现是什么? (答案没那么简单)

标签: c undefined-behavior


【解决方案1】:

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

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

【讨论】:

  • 引用的文本是无意义的递归:如果标准说行为未定义,那么[标准说]行为未定义。更准确的说法是将“未定义的行为”替换为其实际定义:“标准没有要求的行为
【解决方案2】:

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


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

【讨论】:

  • 这里的复杂情况是,许多实现可能表示寄存器中的值与内存中的值不同,即使所有可以放入内存的位模式都表示有效值,寄存器也有可能保存位模式没有。例如,uint16_t 类型的未初始化自动对象可能表现得好像它包含 0..0xFFFF 范围之外的值。
【解决方案3】:

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 字段,that 可能会触发不需要的陷阱处理程序,但如果没有对该字段进行任何操作,则应该没有问题。

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

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

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

是否应该相信 gcc 和 clang 在给定的代码依赖于标准未强制要求的行为时不会出现奇怪的行为,或者甚至会执行标准定义但在没有优化的情况下等同于那些的事情那不是吗?不。如果一些编译器有效地处理行为而其他编译器试图避免这样做并查看标准试图将行为明确定义为标准中的缺陷的少数情况,那么标准是否定义特定行为真的无关紧要。

【讨论】:

    猜你喜欢
    • 2016-09-15
    • 1970-01-01
    • 2011-05-20
    • 2010-09-28
    • 2013-04-18
    • 1970-01-01
    • 2020-06-13
    • 2011-01-19
    相关资源
    最近更新 更多