在C中,假设int
是32位,
uint64_t x = 1000000u * 1000000u;
通常通过将数字乘以32位结果,丢弃溢出位,然后以零扩展名分配给x
来实现。
语言是否可以保证这一点,还是编译器可以选择以64位完成所有操作,从而给出数学上准确的结果?
(我知道语言标准首先允许int
使用64位。我是专门讨论int
是32位的情况。)
答案 0 :(得分:4)
uint64_t x = 1000000u * 1000000u;
您陈述的假设是int
是32位。为了100%清晰,我们需要假设UINT_MAX
是2 32 -1或4294967295
。 (该标准要求unsigned int
的范围至少为0到65535,这意味着至少有16位的大小-但它可以具有 padding bits 。)几乎可以肯定同样的事情(我不知道任何具有填充位的C实现),但是如果我们要问标准保证什么,我们应该明确。
鉴于该假设,常量1000000u
的类型为unsigned int
,相乘的结果为3567587328u
,类型也为unsigned int
。该值将转换为uint64_t
(不丢失任何信息)并存储在x
中。
uint64_t
被保证为正好64位宽,没有填充位。没有满足这些条件的类型的实现将不会定义uint64_t
,因此上述内容将无法编译。 (当然,我假设这是指标准uint64_t
或<stdint.h>
标头中定义的<inttypes.h>
。)
算术表达式的一般规则是,表达式的类型由其操作数的类型决定,而不是由其操作数的值或其出现的上下文确定。如果我们放弃关于unsigned int
上限的假设,则常量1000000u
的类型可以为unsigned int
或unsigned long
,无论哪种类型足以容纳它-产品的值与常量的类型相同,即使这会导致 overflow 环绕。 (严格来说,未签名的操作不会溢出。)
答案 1 :(得分:3)
是的,如果操作数为32位宽,则标准对此进行保证。在C11中,the Standard says:
涉及无符号操作数的计算永远不会溢出,因为不能用所得的无符号整数类型表示的结果的模数要比该所得的类型可以表示的最大值的模数大。 >
在C11的附件H中重申:
C的无符号整数类型在LIA-1意义上是“模”,即溢出或越界结果会自动换行。
因此,溢出的无符号乘法的结果是丢弃结果的高位。
如果您不希望出现这种情况,解决方法是将至少一个常量写为1000000ULL
,以确保将中间结果提升为至少64位。您还可以在其中一个操作数前面添加显式强制转换,例如(uint_fast64_t)1000000
(如果需要)。
如果要保证它,请将两个操作数都写为((uint32_t)1000000UL)
。这种类型(如果存在),必须恰好是32位宽。
但是,我不建议您的程序隐式依赖于实现之间不同的细微隐式行为。对于使用以下代码进行维护的任何人,您都可以更清楚地了解它:
static const uint32_t multiplicand = 1000000UL;
static const uint64_t product_low_word = multiplicand * multiplicand; // Upper 32 bits cleared.
答案 2 :(得分:3)
是的,假设unsigned int
是32位。
每6.5.5 Multiplicative operators, paragraph 3:
通常对操作数进行算术转换。
每个6.3.1.8 Usual arithmetic conversions的转化次数是:
此模式称为通常的算术转换:
- 首先,如果一个操作数的相应实型为long double,则将转换另一个操作数,而不会更改类型域, 到对应的实型为long double的类型。
- 否则,如果一个操作数的相应实型为double,则将转换另一个操作数,而不会更改类型域, 到对应的实型为double的类型。
- 否则,如果一个操作数的相应实型为float,则将转换另一个操作数,而不会更改类型域, 到对应的实型为float.62的类型)
- 否则,对两个操作数执行整数提升。然后,将以下规则应用于提升后的操作数:
- 如果两个操作数具有相同的类型,则无需进一步转换。
- 否则,如果两个操作数都具有符号整数类型或都具有无符号整数类型,则具有较小整数类型的操作数 转换等级转换为更大的操作数类型 排名。
- 否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的秩,则 带符号整数类型的操作数将转换为 无符号整数类型的操作数。
- 否则,如果带符号整数类型的操作数的类型可以表示带无符号操作数类型的所有值 整数类型,然后将转换为无符号整数类型的操作数 到带符号整数类型的操作数的类型。
- 否则,两个操作数都将转换为与带符号整数的操作数类型相对应的无符号整数类型 类型。
注意:“如果两个操作数具有相同的类型,则无需进一步转换。”
由于两个操作数的类型相同-unsigned int
,因此对于任何符合条件的编译器,乘法运算的结果将为unsigned int
。
EXAMPLE 2 from 5.1.2.3 Program execution提供了关于必须严格对待算术运算的信息(EXAMPLE 6甚至更好,但是IMO太长,无法有效引用):
示例2在执行片段时
char c1, c2; /* ... */ c1 = c1 + c2;
“整数提升”要求抽象机提升 将每个变量的值设置为int大小,然后将两个int和 截断总和。只要可以添加两个字符 无溢出,或无声环绕以产生 正确的结果,实际执行只需产生相同的结果 结果,可能会忽略促销。
答案 3 :(得分:2)
无论您使用的C实现中的类型为unsigned
,都需要将其作为模块化算术进行。假设它是32位类型,则结果必须是32位。这不一定适用于其他可能适用不同类型提升规则的表面类似示例。
答案 4 :(得分:2)
操作1000000u * 1000000u
优先于分配,并且独立于分配而发生。 unsigned int
是32位,无论x
的类型如何,这都是32位无符号结果。