论未定义的行为

时间:2011-05-23 22:37:55

标签: c undefined-behavior

一般来说,UB被认为是必须避免的东西,目前的C标准本身列出了附录J中的一些例子。

然而,在某些情况下,除了牺牲可移植性之外,我认为利用UB没有任何害处。

考虑以下定义:

int a = INT_MAX + 1;

评估此表达式会导致UB。但是,如果我的程序打算在32位CPU上运行,模块化算术代表二进制补码中的值,我倾向于相信我可以预测结果。

在我看来,UB有时只是C标准告诉我的方式:“我希望你知道你在做什么,因为我们无法保证会发生什么。”

因此我的问题是:即使C标准认为它要调用UB,或者无论在什么情况下都要避免“UB”,有时依赖依赖于机器的行为是否安全?

7 个答案:

答案 0 :(得分:15)

,除非您同时保持编译器,否则编译器文档会定义其他未定义的行为。

未定义的行为意味着您的编译器可以忽略代码以获取任何的原因,让您认为应该 有时这是为了优化,sometimes it's because of architecture restrictions like this


我建议您阅读this,其中包含确切示例。摘录:

  

有符号整数溢出:

     

如果int类型的算术(例如)溢出,则结果未定义。一个例子是 INT_MAX + 1不能保证INT_MIN 。此行为允许某些类别的优化对某些代码很重要。

     

例如,知道INT_MAX + 1未定义,可以优化X + 1 > Xtrue。知道乘法“不能”溢出(因为这样做将是未定义的)允许优化X * 2 / 2X。虽然这些看似微不足道,但这些事情通常都是通过内联和宏观扩展来揭示的。这允许的更重要的优化是{{​​1}}循环,如下所示:

<=
     

在这个循环中,如果溢出时for (i = 0; i <= N; ++i) { ... } 未定义,编译器可以假设循环将精确迭代N + 1次,这样就可以启动广泛的循环优化。另一方面,如果变量被定义为在溢出时回绕,则编译器必须假定循环可能是无限的(如果iN则会发生这种情况) - 然后禁用这些重要的循环优化。这特别影响64位平台,因为很多代码使用INT_MAX作为归纳变量。

答案 1 :(得分:5)

没有

编译器在优化代码时利用未定义的行为。一个众所周知的例子是GCC编译器中的严格溢出语义(搜索strict-overflow here)例如,这个循环

for (int i = 1; i != 0; ++i)
  ...

据说依赖于有符号整数类型的“机器相关”溢出行为。但是,严格溢出语义规则下的GCC编译器可以(并且将)假设递增int变量只能使它更大,并且永远不会更小。这个假设将使GCC优化算法并产生无限循环

for (;;)
  ...

因为这是未定义行为的完美有效表现。

基本上,在C语言中没有“机器依赖行为”这样的东西。所有行为都由实现决定,实现级别是您可以达到的最低级别。实施将您与原始机器隔离开来并完美隔离您。除非实现明确允许您这样做,否则无法突破隔离并进入实际的原始机器。有符号整数溢出通常不是允许您访问原始机器的上下文之一。

答案 2 :(得分:2)

如果您知道您的代码只针对特定的体系结构,编译器和操作系统,并且您知道未定义的行为是如何工作的(并且不会改变),那么使用它本身并不是错误的它偶尔会。在你的例子中,我想我也可以告诉你将会发生什么。

但是,UB很少是首选解决方案。如果有更清洁的方式,请使用它。使用未定义的行为绝对不是绝对必要的,但在少数情况下可能会很方便。永远不要依赖它。和往常一样,如果你依赖UB,请评论你的代码。

并且,请不要发布依赖于未定义行为的代码,因为当它们在具有与您所依赖的实现不同的实现的系统上编译时,它最终会在某人的脸上爆炸。 / p>

答案 3 :(得分:2)

一般来说,最好完全避免它。另一方面,如果您的编译器文档明确指出为该编译器定义了标准UB的特定事物,您可以利用它,可能添加一些#ifdef / #error机制来阻止在使用另一个编译器的情况下进行编译。

答案 4 :(得分:2)

如果C(或其他语言)标准声明某些特定代码在某些情况下将具有未定义行为,则意味着C编译器可以生成代码以在该情况下执行任何操作,同时保持符合那个标准。许多特定的语言实现都记录了超出通用语言标准所要求的行为。例如,Whizbang Compilers Inc.可能会明确指定其memcpy的特定实现将始终按地址顺序复制单个字节。在这样的编译器上,代码如:

  unsigned char z[256];
  z[0] = 0x53;
  z[1] = 0x4F;
  memcpy(z+2, z, 254);

将具有由Whizbang文档定义的行为,即使此类代码的行为未由任何非供应商特定的C语言规范指定。此类代码与符合Whizbang规范的编译器兼容,但可能与符合各种C标准但不符合Whizbang规范的其他编译器不兼容。

在很多情况下,尤其是嵌入式系统,程序需要做一些C标准不需要编译器允许的事情。编写此类程序是不可能与所有符合标准的编译器兼容的,因为某些符合标准的编译器可能无法提供任何方法来执行需要完成的操作,甚至那些可能需要不同语法的编译器。尽管如此,编写可由任何符合标准的编译器正确运行的代码通常具有相当大的价值。

答案 5 :(得分:0)

如果标准表明做某事是未定义的,那么它是未定义的。您可能认为您可以预测结果会是什么,但您不能。对于特定的编译器,您可能总是得到相同的结果,但对于编译器的下一次迭代,您可能不会。

未定义的行为是如此容易避免 - 不要写那样的代码!那么为什么像你这样的人想搞砸呢?

答案 6 :(得分:0)

没有!仅仅因为它编译,运行并提供希望的输出并不能使其正确。