关于未定义行为

| 通常,UB被认为是必须避免的事情,当前的C标准本身在附录J中列出了很多示例。 但是,在某些情况下,除了牺牲可移植性外,在使用UB方面我看不出任何危害。 考虑以下定义:
int a = INT_MAX + 1;
计算该表达式将导致UB。但是,如果我的程序打算在具有模块化算法的32位CPU上运行,该算法代表Two's Complement中的值,我倾向于相信我可以预测结果。 在我看来,UB有时只是C标准告诉我的方式:“我希望您知道自己在做什么,因为我们无法保证会发生什么。” 因此,我的问题是:即使C标准认为它依赖于调用UB,有时还是依赖于机器相关的行为是安全的,还是不管情况如何,都必须避免使用“ UB”?     
已邀请:
不,除非您也要保持编译器相同,并且您的编译器文档定义了其他情况下未定义的行为。 未定义的行为意味着您的编译器可以出于任何原因忽略您的代码,从而使您认为不应该的事情成为事实。 有时这是为了优化,有时是因为这样的体系结构限制。 我建议您阅读此内容,以解决您的确切示例。摘录:   有符号整数溢出:      例如,如果对“ 1”类型的算术溢出,则结果不确定。一个例子是
INT_MAX + 1
不能保证是
INT_MIN
。此行为启用对某些代码很重要的某些优化类别。      例如,知道未定义
INT_MAX + 1
可以将optimizing5ѭ优化为
true
。知道乘法“不能”溢出(因为这样做是不确定的)可以将
X * 2 / 2
优化为
X
。尽管这些看起来微不足道,但通常通过内联和宏扩展来暴露这些事情。允许的一个更重要的优化是针对这样的ѭ9like循环:
for (i = 0; i <= N; ++i) { ... }
     在此循环中,如果未在溢出时定义
i
,则编译器可以假定该循环将精确地迭代
N + 1
次,这允许进行广泛的循环优化。另一方面,如果定义了变量以在溢出时回绕,则编译器必须假定循环可能是无限的(如果
N
INT_MAX
,则会发生这种情况)-然后禁用这些重要的循环优化。这特别影响64位平台,因为太多代码使用ѭ1作为归纳变量。     
没有。 优化代码时,编译器会利用未定义的行为。一个著名的例子是GCC编译器中的严格溢出语义(在此处搜索
strict-overflow
)。例如,该循环
for (int i = 1; i != 0; ++i)
  ...
据说依赖于您的“与机器有关”的有符号整数类型的溢出行为。但是,在严格的溢出语义规则下,GCC编译器可以(并且会假设)递增
int
变量只会使其变大,而永远不会变小。这个假设将使GCC优化算法并生成无尽的循环
for (;;)
  ...
因为这是未定义行为的完全有效表现。 基本上,没有C语言中的“与机器有关的行为”之类的东西。所有行为都由实现决定,实现的级别是您所能达到的最低级别。实现将您与原始计算机隔离开来,并完美地隔离了您。除非实现明确允许您这样做,否则无法突破这种隔离并进入实际的原始计算机。通常,带符号整数溢出不是允许您访问原始计算机的那些上下文之一。     
如果您知道自己的代码仅针对特定的体系结构,编译器和操作系统,并且知道未定义的行为是如何工作的(并且这种行为不会改变),那么这并不是天生的错误。偶尔使用它。在您的示例中,我想我也可以说会发生什么。 但是,UB很少是首选的解决方案。如果有更清洁的方法,请使用它。使用未定义的行为实际上绝对不是绝对必要的,但是在某些情况下可能很方便。永远不要依赖它。和往常一样,如果您依赖UB,请注释您的代码。 而且,请不要发布依赖于未定义行为的代码,因为当人们在与您所依赖的实现不同的系统上编译该代码时,它最终只会在某人面前大张旗鼓。 。     
通常,最好完全避免它。另一方面,如果您的编译器文档明确声明为该编译器定义了特定于标准的UB,则您可以利用它,可能会添加一些
#ifdef
/
#error
机制来阻止编译,以防使用其他编译器。     
如果C(或其他语言)标准声明某些特定代码在某些情况下将具有“未定义的行为”,则意味着C编译器可以生成代码以在该情况下执行其所需的任何操作,同时仍符合该标准。许多特定的语言实现已记录了行为,这些行为超出了通用语言标准的要求。例如,Whizbang Compilers Inc.可能明确指定其memcpy的特定实现将始终按地址顺序复制单个字节。在这样的编译器上,代码如下:   无符号字符z [256];   z [0] = 0x53;   z [1] = 0x4F;   memcpy(z + 2,z,254); 即使没有任何非特定于供应商的C语言规范指定此类代码的行为,也将具有Whizbang文档定义的行为。这样的代码将与符合Whizbang规范的编译器兼容,但可能与符合各种C标准但不符合Whizbang规范的其他编译器不兼容。 在很多情况下,尤其是在嵌入式系统中,程序将需要执行C标准不需要编译器允许的某些操作。不可能编写这样的程序来与所有符合标准的编译器兼容,因为某些符合标准的编译器可能无法提供任何方式来完成需要完成的工作,甚至那些必须遵循不同语法的程序也是如此。尽管如此,编写可被任何符合标准的编译器正确运行的代码通常具有相当大的价值。     
如果标准说做某事是不确定的,那么它就是不确定的。您可能会想,您可以预测结果会是什么,但是您无法。对于特定的编译器,您可能总是得到相同的结果,但是对于编译器的下一次迭代,则可能不会。 未定义的行为非常容易避免-不要编写这样的代码!那么,为什么像您这样的人想弄混呢?     
没有!仅仅因为它会编译,运行并提供您希望的输出并不能使它正确。     

要回复问题请先登录注册