整数转换(缩小,扩大),未定义的行为

Posted

技术标签:

【中文标题】整数转换(缩小,扩大),未定义的行为【英文标题】:Integer conversions(narrowing, widening), undefined behaviour 【发布时间】:2013-10-16 21:47:44 【问题描述】:

我很难以易于理解的方式找到有关此主题的信息,因此我要求对我发现的内容进行审查。这完全是关于转换和转换。


在我将提到的示例中:

(signed/unsigned) int bigger;
(signed/unsigned) char smaller;

    截断整数。 (大->小)

    第一个截断biggerMSB一侧以匹配smaller的大小。 第二,convert截断结果为有符号/无符号,具体取决于较小的类型。

    如果较大的值太大而无法放入较小的类型,则会导致未定义的行为(请纠正我)。然而,我的规则应该适用于所有机器(也请纠正我)并且结果应该是可预测的。

    加宽整数(更小->更大)

    a) signed char -> signed int

    在较小的前面加上 MSB(1 或 0)以匹配较大的尺寸 转换为签名

    b) signed char -> unsigned int

    在更小的前面加上 MSB(1 或 0)以匹配更大的大小。 转换为无符号

    c) unsigned char -> signed int

    前面加 0 以匹配更大的尺寸 转换为签名

    d) unsigned char -> unsigned int

    前面加 0 以匹配更大的尺寸 转换为无符号

我没有提到的未定义/未指定的行为会在哪里弹出?

【问题讨论】:

这个问题太宽泛了。整数转换取决于操作中存在的操作数和运算符。为了正确回答这个问题,答案必须解决整数类型 (C11 6.2.6.2)、整数提升 (C11 6.3.1.1)、有符号/无符号转换 (C11 6.3.1.3)、通常的算术转换 (C11 6.3. 1.8),赋值(C11 6.5.16.1)......等等。 当我不知道该主题有多广泛时,我怎么知道该主题有多广泛?不过,感谢您提供标准阅读。 【参考方案1】:

整数转换永远不会产生未定义的行为(它可以产生实现定义的行为)。

转换为可以表示正在转换的值的类型始终是明确定义的:值保持不变。

到无符号类型的转换始终是明确定义的:该值取模 UINT_MAX+1(或目标类型允许的任何最大值)。

转换为无法表示正在转换的值的有符号类型会导致实现定义的值或实现定义的信号。

请注意,上述规则是根据整数值定义的,而不是根据位序列定义的。

【讨论】:

所以从任何类型(有符号/无符号/更大/更小)到无符号的转换总是定义为value%(maximum for type being converted to +1) ? 通过将“UB”更改为“未定义的行为”并将“模 UINT_MAX+1(或目标类型允许的任何最大值)”更改为“比最大值多一模,可以改进此答案目标类型的值(例如,65536 表示 16 位无符号短整型,其中最大值为 65535)”。 (这个提议的新措辞仍然很麻烦,但我强烈建议避免使用UINT_MAX,因为这是unsigned int 的特定值,但此语句也适用于其他类型。) 这个表达式不等同于我用 0 截断/前置但更正式的方式吗? @EricPostpischil:事实上,我已经尽量避免措辞过于冗长。会再考虑的... @n.m.:标准不需要符号类型的二进制表示是什么意思?值位对应于无符号类型中的对应项,恰好有一个符号位否定该值或具有 -(2^M) 或 -(2^(M)-1) 的值,并且可能存在填充位。那怎么不是二进制表示? (当然,OP 的符号扩展不适用于符号幅度表示)【参考方案2】:

来自 C 标准文档(我相信第 50 页草稿版本 201x,而不是准确引用):

没有两个有符号整数的秩相同

有符号整数的秩应大于任何精度较低的有符号整数的秩。

long long int 大于 long int 大于 int 大于 short int 大于signed char。

具有相同精度的有符号和无符号具有相同的等级(例如:有符号整数与无符号整数具有相同的等级)

任何标准整数类型的等级都应大于任何相同宽度的扩展整数类型的等级。

char的秩等于unsigned char等于signed char。

(我省略了 bool,因为您将它们排除在您的问题之外)

任何扩展有符号整数相对于另一个扩展有符号整数的排名由实现定义,但仍受其他整数转换排名规则的约束。

对于所有整数类型 T1 T2 和 T3,T1 的排名高于 T2 且 T2 的排名高于 T3,T1 的排名高于 T3。

具有整数类型(除 int 和signed int 外)的对象,其整数等级小于或等于 int 和 unsigned int 等级,_Bool、int、signed int 或 unsigned int 类型的位字段;如果 int 可以表示原始类型的所有值,则将该值转换为 int。否则为无符号整数。所有其他类型都由整数提升更改。

简单来说:

任何比 int 或 unsigned int “更小”的类型在转换为其他更高级别的类型时都会被提升为 int。这是编译器的工作,以确保为给定机器(架构)编译的 C 代码在这方面符合 ISO-C。 char 是实现定义的(有符号或无符号)。所有其他类型(升级或“降级”)都是实现定义的。

什么是实现定义的?这意味着给定的编译器将在给定的机器上系统地表现相同。换句话说,所有“实现定义的”行为都取决于编译器和目标机器。

制作可移植代码:

始终将值提升到更高级别的标准 C 类型。 切勿将值“降级”为较小的类型。 避免在您的代码中使用所有“实现定义的”实现。

如果它破坏了程序员的努力,为什么存在实现定义的疯狂?系统编程基本上需要这些实现定义的行为。

所以更具体地针对您的问题:

截断很可能是不可转移的。或者将需要在维护、错误跟踪等方面付出更多的努力,而不是简单地使用更高级别的类型来维护代码。 如果您的实现运行的值大于所涉及的类型,则您的设计是错误的(除非您参与系统编程)。 根据经验,从无符号到有符号会保留值,但反之则不然。因此,当一个无符号值与一个有符号值并驾齐驱时,将无符号值提升为有符号值,而不是相反。 如果在您的应用程序中使用尽可能小的整数类型对内存至关重要,您应该重新审视整个程序的架构。

【讨论】:

有两种类型的隐式整数转换:您在此处描述的整数提升,以及您根本没有提到的平衡(“通常的算术转换”)。 我刚刚重新检查了标准,我的理解是上述规则确实涵盖了这两种情况。但是,在类型定义部分(出现在此部分之前)中,有一些关于有符号与无符号的规则,但范围不仅仅是整数,并且不会使上述经验法则无效。如果我遗漏了标准中的某个部分,我很乐意相应地查看我的答案。 不,你完全忽略了平衡。您描述的整数提升发生在平衡之前。例如,如果您有一个参数为(char) + (long) 的操作,则整数提升首先提升字符,给出(int) + (long)。平衡然后将 int 提升为 long,计算将在类型 (long) + (long) 上完成。请参阅标准 6.3.1.8。 6.3.1.8 关注双倍。整数部分是我引用的部分,它适用于简单的做作(例如:int a = b,其中 be 是一个字符)和任何算术运算。请注意,该措辞与操作无关,并且发生操作优先级。我上面的文字涵盖了您给出的示例。我支持我上面的其他评论......

以上是关于整数转换(缩小,扩大),未定义的行为的主要内容,如果未能解决你的问题,请参考以下文章

左移是 Rust 中的负值未定义行为吗?

是否通过强制转换为有符号的未定义行为来检测无符号环绕?

C++ SSE:存储到数组后的未定义行为

如何实现避免未定义行为的侵入式链表?

为什么printf(“%f”,0);给出未定义的行为?

自定义 UITableView 单元格图像转换未应用于可重用单元格