为什么将“超出范围的整数转换为整数"会导致 IB,但将“超出范围的浮点数转换为整数"会导致 UB? [英] Why converting 'out of range integer to integer' leads to IB, but converting 'out of range floating-point to integer' leads to UB?

查看:23
本文介绍了为什么将“超出范围的整数转换为整数"会导致 IB,但将“超出范围的浮点数转换为整数"会导致 UB?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下问题:

  1. 类型转换:double to char:多个问题
  2. 将无符号值赋给有符号字符

上下文:ISO/IEC 9899:202x (E) 工作草案 - 2020 年 2 月 5 日 C17..C2x N2479(已添加重点):

<块引用>

J.3 实现定义的行为,J.3.5 整数

——当值不能在该类型的对象中表示时,将整数转换为有符号整数类型的结果或引发的信号 (6.3.1.3).

<块引用>

6.3.1.4 实数浮点数和整数

当标准浮点类型的有限值转换为 _Bool 以外的整数类型时,小数部分被丢弃(即,该值被截断为 0).如果整数部分的值不能用整数类型表示,行为未定义.

问题:为什么将超出范围的整数转换为整数"会导致 IB,而将超出范围的浮点数转换为整数"会导致 UB?IE.为什么行为不一致(例如两种情况下的 IB)?

UPD.来自用户 P.P. 的回答在 重复问题:

<块引用>

我怀疑它是否可以合理回答.这主要是因为历史,并且基于C标准化时的实现,硬件行为等.所以一致性"不可能/不切实际(这不像委员会决定任意将某些行为归类为 IB、UB 或未指定).

解决方案

从标准的角度来看,是否将某事物分类为实现定义行为和未定义行为的问题取决于是否应该要求所有实现记录通常与语言语义一致的行为,无论成本或有用性如何.没有必要强制实施以客户认为有用的方式处理操作,因为预期允许以这种方式运行的实施无论是否有授权都会这样做.因此,将实现可能 100% 一致地处理的有用操作描述为未定义行为比将有时可能无法一致实施的实现定义的操作描述为更好.

请注意,对于将操作视为具有已记录行为的实现而言,有时可能会产生不明显的成本.例如:

int f1(int x, int y);int f2(int x, int y, int z);无效测试(int x,无符号字符 y){短温度 = x/(y+1);如果 (f1(x,y))f2(x,y,温度);}

在转换为 short 始终执行且没有副作用的平台上,或在允许将超出范围的转换视为未定义行为的实现上,x/(y+1) 的计算和转换为 short 可以被推迟到调用 f1 之后,如果 f1 返回零,则完全跳过.然而,这种转换可能会影响由转换引发的信号的行为,因此在转换可能引发信号的实现标准下似乎是不允许的.

另一方面,虽然在越界转换的情况下让实现发出信号可能很有用,但此类信号主要在诊断质量被视为比性能更重要的情况下有用.如果将转换处理为没有副作用,则性能更重要的实现可以自由地进行上述优化,并且似乎后一种操作过程在所有平台上都是可行的.

在某些平台上,将 float 转换为 int 的最快方法会陷入困境;如前所述,一个动作可能陷入的可能性会使分类为实现定义的行为代价高昂.虽然不太可能有任何平台处理来自例如的转换是不切实际的.floatshort 作为从 floatint 的转换,然后是从 intshort,有些平台可能不是最有用的行为(例如,如果平台可以免费将这种转换的结果与目标类型的范围挂钩,那么可能比转换为 int 然后是目标类型更有用).即使该标准的作者预期并打算从浮点类型转换为小整数类型永远不会为 int 范围内的任何值产生未排序的陷阱,该标准仍将其归类为通用 UB在某些情况下可能表现出不可预测的行为,但在其他情况下以可预测的特定于实现方式的行为,没有任何努力来确定它们应该表现出可预测的特定情况.

通过检查 C89 和 C99 中描述左移的方式,可能最好地说明后一个原则.x << 没有理由0 不应该为 x 的所有整数值产生 x,而 C89 指定行为的方式正是这样做的.然而,C89 规范在某些情况下指定了行为,允许某些实现以不同的且不一定可预测的方式运行可能很有用.C99 没有努力确定所有实现都应以与 C89 相同的方式处理负数左移的情况,因为作者预计所有实现都会以 C89 方式处理此类情况,无论是否有授权.

Follow-up question for:

  1. Type casting: double to char: multiple questions
  2. Assigning an unsigned value to a signed char

Context: ISO/IEC 9899:202x (E) working draft — February 5, 2020 C17..C2x N2479 (emphasis added):

J.3 Implementation-defined behavior, J.3.5 Integers

— The result of, or the signal raised by, converting an integer to a signed integer type when the value cannot be represented in an object of that type (6.3.1.3).

6.3.1.4 Real floating and integer

When a finite value of standard floating type is converted to an integer type other than _Bool, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined.

Question: Why converting 'out of range integer to integer' leads to IB, but converting 'out of range floating-point to integer' leads to UB? I.e. why the behavior is not consistent (e.g. IB in both cases)?

UPD. Answer from user P.P. in duplicated question:

I doubt it's reasonably answerable. It's mainly because of history, and based on the implementations, behaviours of hardware, etc when C was standardized. So "consistency" wasn't possible/practical (it's not like the committee decided to arbitrarily classify certain behaviours as IB, UB, or unspecified).

解决方案

From the point of view of the Standard, the question of whether to classify something as Implementation-Defined Behavior and Undefined Behavior depends on whether all implementations should be required to document a behavior generally consistent with the semantics of the language, regardless for cost or usefulness. There was no need to mandate that implementations process actions in ways their customers would find useful, because it was expected that implementations allowed to behave in such fashion would do so with or without a mandate. Consequently, it was seen as better to characterize as Undefined Behavior useful actions which implementations might process 100% consistently, than to characterize as Implementation-Defined actions which might sometimes be impractical to implement consistently.

Note that for an implementation to treat an action as having documented behavior could sometimes have costs that might not be obvious. Consider, for example:

int f1(int x, int y);
int f2(int x, int y, int z);
void test(int x, unsigned char y)
{
  short temp = x/(y+1);
  if (f1(x,y))
    f2(x,y,temp);
}

On platforms where the conversion to short would always execute without side effects, or on implementations that were allowed to treat out-of-range conversions as Undefined Behavior, the computation of x/(y+1) and conversion to short could be deferred until after the call to f1, and skipped altogether if f1 returns zero. Such transformation could affect the behavior of a signal raised by the conversion, however, and would thus not appear to be allowable under the Standard on implementations where the conversion could raise a signal.

On the other hand, while it may be useful to have implementations raise a signal in case of an out-of-bounds conversion, such signals would mainly be useful in situations where quality of diagnostics was viewed as more important than performance. Implementations where performance was more important would be free to make optimizations like the above if they processed the conversion as having no side effects, and it seemed likely that the latter course of action would be practical on all platforms.

There were platforms where the fastest way of converting a float to an int will trap; as noted, the possibility that an action may trap would make classification as Implementation-Defined behavior expensive. While it is unlikely that there would have been any platforms where it would have been impractical to process a conversion from e.g. float to short as a conversion from float to int, followed by a conversion from int to short, there are platforms where that may not be the most useful behavior (e.g. if a platform can at no extra cost peg the result of such a conversion to the range of the target type, that may be more useful than a conversion to int and then the target type). Even if the authors of the Standard would have expected and intended that conversions from floating-point types to small integer types never yield unsequenced traps for any values which are within range of int, the Standard classifies as UB general actions which might behave unpredictably in some cases but in a predictable implementation-specific fashion in others, without any effort to identify specific cases where they should behave predictably.

The latter principle is perhaps best illustrated by examining the way left shift was described in C89 and C99. There is no reason why x << 0 shouldn't yield x for all integer values of x, and the way C89 specified the behavior would do precisely that. The C89 spec, however, specified behavior in some cases where it may be useful to allow some implementations to behave in a different, and not necessarily predictable, fashion. C99 makes no effort to identify situations where all implementations should treat left shifts of negative numbers the same way as C89 did, because the authors expected that all implementations would treat such cases in C89 fashion with or without a mandate.

这篇关于为什么将“超出范围的整数转换为整数"会导致 IB,但将“超出范围的浮点数转换为整数"会导致 UB?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆