返回未初始化的,最终未使用的结构是不确定的行为?

时间:2018-06-08 00:03:17

标签: c language-lawyer c11

如果唯一的后续使用是在初始化语句中,如果UB在没有初始化的情况下返回结构,如下所示:

typedef struct { int x; } s;

s callee(void) {
  s ret;
  return ret;
}

void caller() {
  s dummy = callee();
}

2 个答案:

答案 0 :(得分:2)

首先考虑这个类似的代码:

s ret;
s dummy = ret;

结构不能有陷阱表示(C11 6.2.6.1/6)。但是由于Itanium子句(C11 6.3.2.1/2),此代码会导致未定义的行为,该条款表明使用从未获取其地址的未初始化自动对象的值会导致UB。

所以这段代码定义明确:

s ret;
&ret;
s dummy = ret;

有关该条款的进一步阅读,请参阅:Is a^a or a-a undefined behaviour if a is not initialized?

对于具有函数返回值的版本:标准中没有拼写出返回值是否为安腾子句的目的而计为自动对象。我倾向于说它没有,因为标准没有将返回值描述为对象。但是,熟悉Itanium ABI的人可以评论是否通过返回值传递未初始化的结构会触发NaT异常会很好。

代替这一点,我的立场是函数调用版本与上面讨论的赋值版本具有相同的语义,即发布的代码是UB,但添加&ret;使其定义明确。

答案 1 :(得分:-1)

TLDR:虽然功能“通过”呼叫者最终忽略的不确定值的能力提供了在大多数平台上远远超过成本的好处,因此应该由针对此类平台的质量实现提供,标准不要求提供它的实现,因此“聪明”的实现不会。


在编写标准时,所有编译器都会有许多构造,但标准没有明确定义。该标准指出,处理标准不会产生任何要求的行动的一种常见方式是“以文件化的方式表征环境”,但在理由中指出,何时这样做的决定是实施质量问题而不是一致性。理由还认为,实施可能符合要求,但质量很差,无用,但作者认为没有必要花费精力来禁止这种实施。

典型平台的质量编译器有两种合理且有用的方式可以处理如上所述的代码:

  1. 如果函数尝试返回未初始化的对象,编译器可能会捕获实现定义的方法,而不考虑调用代码是否会使用该值[生成陷阱的代码可能没有办法了解调用代码的任何内容]。

  2. 编译器可能会在调用者可能使用或不使用的某个位置留下一些任意的位集合,如果调用者实际上不使用它们则没有副作用。请注意,如果某种类型的对象在存储在存储器中时没有填充位,则存储在寄存器中时可能会有填充位,如果未正确设置这些填充位,则可能会出现奇怪的情况。

  3. 标准的作者没有试图列举实施在处理不确定值时可能做的所有事情,因为他们认为寻求产生高质量实施的实施者将决定哪种行动方案最适合于预期有关实施的平台和目的。

    不幸的是,即使在任何通用平台上,在调用者要忽略它们的情况下,让函数安全地返回不确定的值也几乎没有任何成本,并且在某些情况下这样做会产生比其他方式更高效的代码。 (例如说给出:

    extern volatile int vv1, vv2, vv3;
    int foo(int mode)
    {
      int result;
      vv1 = 1;
      if (mode & 1)
        result = vv2;
      if (mode & 2)
        result = vv3;
      return result;
    }
    

    声明“foo(0);”将存储1存储到vv1而没有其他副作用会让程序员强制编译器产生不必要的负载。编译器编写者找到“聪明”的方法来利用标准不会这样的事实变得流行需要这样的保证。例如,上面的代码可能会“优化”为:

    int foo(int mode)
    {
      vv1 = 1;
      if (!(mode & 2))
        return vv2;
      if (mode & 1)
        +vv2; // Access and ignore value
      return vv3;
    }
    

    这种“优化”的实际价值是否会超过允许程序员允许编译器避免不必要的负载的程序员,无法确定他们的代码只能在高质量的实现上运行的程序员需要考虑“聪明的“那些。