长整型乘法(long long) vs 整型(int)乘法

8

给定以下代码片段:

#include <stdio.h>

typedef signed long long int64;
typedef signed int int32;
typedef signed char int8;

int main()
{
    printf("%i\n", sizeof(int8));
    printf("%i\n", sizeof(int32));
    printf("%i\n", sizeof(int64));

    int8 a = 100;
    int8 b = 100;
    int32 c = a * b;
    printf("%i\n", c);

    int32 d = 1000000000;
    int32 e = 1000000000;
    int64 f = d * e;
    printf("%I64d\n", f);
}

使用MinGW GCC 3.4.5编译时,输出结果为(-O0)。
1
4
8
10000
-1486618624

第一个乘法在内部被转换为int32(根据汇编器输出)。第二个乘法没有被转换。我不确定结果是否因为程序在IA32上运行,或者因为它在C标准中的某个地方被定义了。尽管如此,我很想知道这种确切的行为是否有定义(ISO/IEC 9899?),因为我想更好地理解何时何地需要手动转换类型(我在从另一种架构移植程序时遇到了问题)。

4个回答

8
C99标准确实规定,例如*这样的二进制运算符不作用于小于int的整数类型。在应用运算符之前,这些类型的表达式会被提升为int。请参见6.3.1.4第2段和“整数提升”一词的众多出现次数。但是,这与编译器生成的汇编指令有些无关,因为它们操作int,即使编译器允许计算更短的结果(例如,因为结果立即存储在短类型的l-value中),这也更快。
关于int64 f = d * e;,其中de的类型为int,乘法按照相同的提升规则作为int执行。溢出在技术上是未定义行为,在这里你得到了两个补码结果,但根据标准,你可以得到任何东西。
注意:当提升时,提升规则区分有符号和无符号类型。规则是将较小的类型提升为int,除非int不能表示类型的所有值,在这种情况下,使用unsigned int

6
问题在于乘法是int32 * int32,计算后得到的结果也是int32,然后将该结果赋值给int64。这就好像使用整数除法将3除以2,会被计算为1.0并将其赋值给d,即double d = 3 / 2;
无论何时,你都必须注意表达式或子表达式的类型,因为它可能很重要。这需要确保适当类型的操作被计算为适当类型,例如将其中一个乘数强制转换为int64,或(如我的示例中所示)使用3.0 / 2(float) 3/2

1
说得非常好。@azraiyl 应该将那一行改为 int64 f = (int64)d * e; - Paul Tomblin
很抱歉我没有说明我已经知道这里的解决方案。我感兴趣的是为什么在第一种情况下乘法是int32 * int32而不是int8 * int8。即使CPU只支持int32乘法,它也可以在乘法后转换回int8。但是IA32 imul指令适用于8位寄存器(AL,...)。 - azraiyl
@azrayl:至少在C90中,如果算术操作数是较小的类型,C会将它们提升为“int”。浏览C99标准似乎表明这种情况不再存在,但我并不确定。您使用的是哪个C编译器,如果适用,使用了哪些选项? - David Thornley

3

阅读K&R(原版)。除非涉及变量是(或被转换为)更大的类型,否则所有整数操作都使用自然整数类型完成。对char的操作被转换为32位,因为这是该架构上整数的自然大小。两个32位整数的乘法是在32位中完成的,因为没有将其转换为更大的类型(直到将其分配给64位变量,但那时已经太晚了)。如果您想让操作在64位中发生,请将一个或两个int转换为64位。

int64 f = (int64)d * e;

2

a * b 计算为整数,然后转换为接收变量类型(这恰好是 int)。

d * e 计算为整数,然后转换为接收变量类型(这恰好是 int64)。

如果任何一个类型变量大于 int(或者是浮点数),那么该类型将被使用。但由于所有乘法中使用的类型都是 int 或更小,因此使用了 int 类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接