【问题标题】:c = a + b and implicit conversionc = a + b 和隐式转换
【发布时间】:2015-07-27 12:01:42
【问题描述】:

使用我的编译器,c 是 54464(16 位截断),d 是 10176。 但是对于gccc 是 120000,d 是 600000。

真正的行为是什么?行为是否未定义?还是我的编译器是假的?

unsigned short a = 60000;
unsigned short b = 60000;
unsigned long c = a + b;
unsigned long d = a * 10;

是否有针对这些情况发出警报的选项?

Wconversion 警告:

void foo(unsigned long a);
foo(a+b);

但没有警告:

unsigned long c = a + b

【问题讨论】:

标签: c implicit-conversion


【解决方案1】:

首先,您应该知道,在 C 语言中,标准类型对于标准整数类型没有特定的精度(可表示值的数量)。它只需要每种类型的最低精度。这些导致以下典型位大小standard 允许更复杂的表示:

  • char:8 位
  • short:16 位
  • int: 16 (!) 位
  • long:32 位
  • long long(C99 起):64 位

注意:在limits.h 中给出了实现的实际限制(意味着一定的精度)。

其次,执行操作的类型取决于操作数的类型,而不是赋值左侧的类型(因为赋值也只是表达式)。为此,上面给出的类型按转化排名排序。秩小于int 的操作数首先转换为int。对于其他操作数,将具有较小等级的操作数转换为其他操作数的类型。这些是usual arithmetic conversions

您的实现似乎使用了与unsigned short 大小相同的16 位unsigned int,因此ab 被转换为unsigned int,以16 位执行操作。对于unsigned,运算以 65536 为模(2 的 16 次方)执行 - 这称为回绕(签名类型不需要!)。然后将结果转换为unsigned long 并分配给变量。

对于 gcc,我假设这是为 PC 或 32 位 CPU 编译的。对于 this(unsigned) int 通常有 32 位,而(unsigned) long 至少有 32 位(必需)。因此,操作没有环绕。

注意:对于 PC,操作数转换为int,而不是unsigned int。这是因为int 已经可以代表unsigned short 的所有值; unsigned int 不是必需的。如果操作结果溢出signed int!

,这可能会导致意外(实际上:实现定义)行为

如果您需要定义大小的类型,请参阅 stdint.h(自 C99 起)以获取 uint16_tuint32_t。这些是typedefs 类型,具有适合您的实现的大小。

您还可以将其中一个操作数(不是整个表达式!)转换为结果的类型:

unsigned long c = (unsigned long)a + b;

或者,使用已知大小的类型:

#include <stdint.h>
...
uint16_t a = 60000, b = 60000;
uint32_t c = (uint32_t)a + b;

请注意,由于转换规则,转换一个操作数就足够了。

更新(感谢@chux):

上面显示的演员阵容没有问题。但是,如果a 的转换排名比类型转换大,这可能会将其值截断为较小的类型。虽然这很容易避免,因为所有类型在编译时都是已知的(静态类型),但另一种方法是乘以所需类型的 1:

unsigned long c = ((unsigned long)1U * a) + b

这样,使用类型转换或a(或b)中给出的较大等级。任何合理的编译器都会消除乘法。

另一种方法,甚至可以使用typeof() gcc 扩展名来避免知道目标类型名称:

unsigned long c;

... many lines of code

c = ((typeof(c))1U * a) + b

【讨论】:

  • 从limits.h 到范围表的转换有点突然。详细说明差异:标准列出了可表示的范围。您提供的尺寸表与标准兼容。 limits.h 描述了编译器实现的限制,可以合理地预期它达到或超过表中的值。
  • 详细信息:(uint32_t)a + b; 可能导致a 缩小。 (虽然不是在这个简单的例子中)。 一般,要加宽整数,建议乘以1UL*a + b; 中类型的1,而不是强制转换。这个 apraoch 永远不会导致a 的缩小。
  • @EricTowers:谢谢,作为非母语人士,我有时必须多次编辑文本才能获得良好的措辞。希望编辑现在更好。
  • 现在看起来好多了。即使作为母语人士,我也必须多次编辑我的文本以修正在打字时(考虑到多个上下文线程)和阅读时(仅限于书面上下文)很好的措辞。
  • 还可能值得注意的是标准中应该描述的缺陷:如果int 大于unsigned short,但不超过两倍,则给出unsigned short x = (something); if (x &lt; 46341) do_something(); x*=x; 的编译器C 标准将允许无条件调用 do_something,即使乘法的目的是执行算术模 65536。
【解决方案2】:

a + b 将被计算为unsigned int(它被分配给unsigned long 的事实并不相关)。 C 标准要求这个总和将环绕模“一加最大可能的无符号数”。在您的系统上,unsigned int 看起来是 16 位的,因此结果是以 6​​5536 为模计算得出的。

在另一个系统上,intunsigned int 看起来更大,因此能够容纳更大的数字。现在发生的事情非常微妙(确认@PascalCuoq):因为unsigned short 的所有值都可以在int 中表示,a + b 将被计算为int。 (仅当shortint 的宽度相同,或者以其他方式,unsigned short 的某些值不能表示为int 时,总和才会计算为unsigned int。)

尽管 C 标准没有为 unsigned shortunsigned int 指定固定大小,但您的程序行为是明确定义的。请注意,对于有符号类型,这不是正确的。

最后,您可以使用大小类型uint16_tuint32_t 等,如果您的编译器支持,则保证具有指定的大小。

【讨论】:

  • 请注意,如果程序将a*b 相乘而不是将它们相加,则结果将不会在int 大于unsigned short 但不超过两倍的平台上定义。
【解决方案3】:

在 C 中,charshort(及其未签名的部分)和float 类型应被视为“存储”类型,因为它们旨在优化存储但不是“本机”类型CPU 喜欢的大小,并且它们从不用于计算

例如,当您有两个char 值并将它们放在一个表达式中时,它们首先转换为int,然后执行操作。原因是 CPU 与int 一起工作得更好。 float 也是如此,它总是隐式转换为 double 以进行计算。

在您的代码中,a+b 的计算是两个无符号整数的和;在 C 中,没有办法计算两个无符号短裤的总和......您可以做的是将 最终结果 存储在一个无符号短裤中,由于模数学的属性,它将是相同的.

【讨论】:

    猜你喜欢
    • 2012-02-08
    • 1970-01-01
    • 2011-05-30
    • 2020-07-08
    • 2017-01-05
    • 2012-09-03
    • 2020-07-15
    • 2014-03-23
    • 1970-01-01
    相关资源
    最近更新 更多