c = a + b 和隐式转换

c = a + b and implicit conversion

对于我的编译器,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

a + b 将被计算为 unsigned int(它被分配给 unsigned long 的事实无关紧要)。 C 标准要求此和 环绕 模 "one plus the largest unsigned possible"。在您的系统上,unsigned int 看起来像是 16 位,因此结果是对 65536 取模计算的。

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

虽然 C 标准没有为 unsigned shortunsigned int 指定固定大小,但您的程序行为是明确定义的。请注意,这 not 对于签名类型是正确的。

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

首先,您应该知道在 C 中,标准类型对于标准整数类型没有特定的精度(可表示值的数量)。它只需要每个类型的 minimal 精度。这些导致以下 典型位大小 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。这些是 typedef 大小适合您的实施的类型。

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

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 具有比类型转换更大的转换等级,这可能会将其值截断为较小的类型。虽然这可以很容易地避免,因为所有类型在编译时都是已知的(静态类型),但另一种方法是乘以所需类型中的一种:

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

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

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

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