c = a + b 和隐式转换
c = a + b and implicit conversion
对于我的编译器,c
是 54464(截断 16 位)而 d
是 10176。
但是对于 gcc
,c
是 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 取模计算的。
在另一个系统上,int
和 unsigned int
看起来更大,因此能够容纳更大的数字。现在发生的事情非常微妙(承认@PascalCuoq):因为 unsigned short
的所有值都可以在 int
中表示,a + b
将被计算为 int
。 (只有当 short
和 int
的宽度相同,或者以其他方式,unsigned short
的某些值不能表示为 int
时,总和才会计算为 unsigned int
.)
虽然 C 标准没有为 unsigned short
或 unsigned int
指定固定大小,但您的程序行为是明确定义的。请注意,这 not 对于签名类型是正确的。
最后一点,您可以使用大小类型 uint16_t
、uint32_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
,因此 a
和 b
被转换为 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_t
、uint32_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 中,类型 char
、short
(及其未签名的对应部分)和 float
应被视为 "storage" 类型,因为它们旨在优化存储,但不是 CPU 喜欢的 "native" 大小,它们 从未用于计算 .
例如,当您有两个 char
值并将它们放在一个表达式中时,它们首先被转换为 int
,然后执行操作。原因是 CPU 与 int
配合使用效果更好。 float
也是如此,它总是隐式转换为 double
以进行计算。
在您的代码中,计算 a+b
是两个无符号整数的总和;在 C 中,无法计算两个无符号短裤的总和...您可以做的是将 最终结果 存储在无符号短裤中,由于模数学的特性,将一样。
对于我的编译器,c
是 54464(截断 16 位)而 d
是 10176。
但是对于 gcc
,c
是 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 取模计算的。
在另一个系统上,int
和 unsigned int
看起来更大,因此能够容纳更大的数字。现在发生的事情非常微妙(承认@PascalCuoq):因为 unsigned short
的所有值都可以在 int
中表示,a + b
将被计算为 int
。 (只有当 short
和 int
的宽度相同,或者以其他方式,unsigned short
的某些值不能表示为 int
时,总和才会计算为 unsigned int
.)
虽然 C 标准没有为 unsigned short
或 unsigned int
指定固定大小,但您的程序行为是明确定义的。请注意,这 not 对于签名类型是正确的。
最后一点,您可以使用大小类型 uint16_t
、uint32_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
,因此 a
和 b
被转换为 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_t
、uint32_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 中,类型 char
、short
(及其未签名的对应部分)和 float
应被视为 "storage" 类型,因为它们旨在优化存储,但不是 CPU 喜欢的 "native" 大小,它们 从未用于计算 .
例如,当您有两个 char
值并将它们放在一个表达式中时,它们首先被转换为 int
,然后执行操作。原因是 CPU 与 int
配合使用效果更好。 float
也是如此,它总是隐式转换为 double
以进行计算。
在您的代码中,计算 a+b
是两个无符号整数的总和;在 C 中,无法计算两个无符号短裤的总和...您可以做的是将 最终结果 存储在无符号短裤中,由于模数学的特性,将一样。