【问题标题】:Why is returning a non-initialized value considered undefined behavior?为什么返回一个未初始化的值被认为是未定义的行为?
【发布时间】:2017-05-15 13:19:25
【问题描述】:

在阅读this的时候看到了一个不明白的UB,希望你能澄清一下

size_t f(int x)
{
    size_t a;
    if(x) // either x nonzero or UB
        a = 42;
    return a; 
}

我猜 UB 是由于 a 没有初始化值,但这不是定义的行为吗?意思是,f(0) 将返回变量a 保存的值,无论它是什么(我认为这类似于rand())。我们必须知道代码 sn-p 返回什么值才能使代码具有良好定义的行为?

【问题讨论】:

  • rand() 的返回值是基于srand() 定义的。您将无法知道a 是什么,因此未定义
  • 除了 Sourav 的回答:不要忘记编译器可以假设任何它想要的东西。而且它不会/不可能发生。在您的示例中,编译器可能会决定 UB 不可能发生,然后 x 将始终为非零。您可能会将其解读为:如果x0,那么我可以做任何我想做的事情,然后我的行为就像它是非零的并且(如在您的链接示例中)总是返回 42 (或格式化您的硬盘驱动器,如果生成代码稍微快一点...)
  • Undefinedrandom 不同。
  • @ErikW "...因为它基于 srand()"?这完全无关紧要。
  • @ErikW 这是不正确的。 rand 自己定义了行为;无需使用srand

标签: c initialization undefined-behavior


【解决方案1】:

意思是,f(0) 将返回变量 a 所持有的值,不管它是什么......

嗯,在你的情况下,

所以,是的,根据定义,这会导致undefined behavior

引用C11,第 6.3.2.1 章

[...] 如果 左值指定一个自动存储持续时间的对象,该对象可能已经 使用寄存器存储类声明(从未占用其地址),以及该对象 未初始化(未使用初始化程序声明且未对其赋值 在使用前执行),行为未定义。


【讨论】:

  • 试图在这里理解... 1) a 是自动局部变量,我没有初始化它,因此是 UB? 2)试图在这里阅读(stackoverflow.com/a/6725981/3512538)但不明白,你能详细说明一下吗? 3)我不明白为什么这是相关的。您的意思是 a 没有地址,因此它不能保存值,因此不能保存 UB?
  • @CIsForCookies 仔细、完整地阅读和理解文本。不要只选择一些关键字!
  • 就第 6.3.2.1 段而言,对象的类型是否提供陷阱表示并不重要。
  • @JohnBollinger 但第 6.3.2.1 段是否将原因 1 和 3 联系在一起?我知道未初始化的自动局部变量会导致 UB,但它是否连接到它的(不存在的)地址?如果是,怎么做?
  • 有趣。标准中的那句话似乎意味着int a; int *p = &a; return a; 由于使用了& 而没有表现出UB。我敢肯定,这不可能是预期的。我想知道是否还有其他要求仍然被违反。 (我认为'注册'资格的意图是scanf("%d", p);scanf("%d", &a);(没有变量p)不一定会导致UB。)但感觉有点像边缘情况.
【解决方案2】:

作为@SouravGhosh 回答的补充,重要的是要了解,具有未定义行为是语言结构的某些组合和程序可能执行的某些运行时评估的属性,标准规定 em>。它不是分析编译器或程序可能做什么的功能;事实上,恰恰相反:对编译器和程序的许可,使它们不受任何特定约束。

因此,尽管该标准在声明 UB 方面相当合乎逻辑且一致,但从质疑特定构造为何具有 UB 或为何特定评估可能或确实显示 UB 的方向来解决问题并没有多大用处。 标准指定它的作用的原因,但为什么事物具有 UB 的主要答案始终是“因为标准是这样说的。”

【讨论】:

    【解决方案3】:

    未定义行为是实现以任何作者认为最适合预期目的的方式处理代码的许可。一些实现包括在没有先写入自动变量的情况下进行陷阱的逻辑,即使这些类型没有陷阱表示;该标准的作者几乎可以肯定地意识到这种行为并认为它是有用的。该标准仅指定了一种可能会陷入陷阱的情况,但仅以定义的方式(从较大的整数类型转换为较小的整数类型);在所有其他可能陷入陷阱的情况下,标准的作者只是简单地保留了未定义的行为,而不是尝试详细了解特定陷阱的工作原理、它们是否可恢复等。

    此外,自动变量通常映射到大于相关变量的寄存器,在这种情况下,即使是没有陷阱表示的类型也可能表现异常。例如:

    volatile uint16_t v;
    uint32_t x(uint32_t a, uint32_t b)
    {
      uint16_t temp;
      if (b) temp=v;
      return temp;
    }
    

    如果 b 不为零,则 temp 将加载 v,加载 v 将导致 temp 保持某个值 0-65535。但是,如果 b 为零,则编译器无法加载 tempv(因为 volatile 限定符)。如果 temp 已分配给一个 32 位寄存器(在某些平台上,它可能在逻辑上分配给用于 a 的相同寄存器),则该函数可能表现得好像 temp 持有一个大于 65535 的值. 标准允许这种可能性的最简单方法是说在上述情况下返回temp 将是未定义的行为。不是因为它期望实现会在调用者最终忽略返回值的情况下做任何特别不稳定的事情(如果调用者要使用返回值,调用者可能不会传递 b==0)但是因为将事情留给实施者判断比试图为这些事情制定完美的一刀切规则更容易。

    现代 C 实现者不再将未定义行为视为进行判断的邀请,而是将其视为不需要判断的邀请。因此,它们的行为可能会破坏程序执行,即使未初始化值的值没有任何用途,只是通过不知道它是否有意义的代码传递给最终忽略它的代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-11
      • 2016-09-01
      • 2013-09-13
      • 2020-01-18
      • 1970-01-01
      相关资源
      最近更新 更多