我目前正在修复C代码中的遗留错误。在修复此错误的过程中,我将unsigned int
存储到了unsigned long long
。但令我惊讶的是,当我在64位版本的GCC
上编译此代码时,数学停止了工作。我发现问题是,当我分配long long
一个int值时,我得到一个看起来像0x0000000012345678
的数字,但是在64位机器上,这个数字变为0xFFFFFFFF12345678
有人可以向我解释或指出某些关于在较大的数据类型中存储较小数据类型时应该发生什么的规范或文档吗?也许在C中执行此操作的适当模式是什么?
更新 - 代码示例
这就是我正在做的事情:
// Results in 0xFFFFFFFFC0000000 in 64 bit gcc 4.1.2
// Results in 0x00000000C0000000 in 32 bit gcc 3.4.6
u_long foo = 3 * 1024 * 1024 * 1024;
答案 0 :(得分:18)
我认为你必须告诉编译器右边的数字是无符号的。否则它认为它是一个普通的signed int,并且由于符号位被设置,它认为它是负的,然后它将它符号扩展到接收器中。
右边有一些未签名的演员。
答案 1 :(得分:12)
表达通常是独立评估的;他们的结果不会受到他们出现的背景的影响。
像1024
这样的整数常量是其值适合的int
,long int
,long long int
中的最小值;在1024
的特定情况下,总是int
。
我在此假设u_long
是unsigned long
的typedef(尽管您在提问中也提到了long long
)。
所以给出:
unsigned long foo = 3 * 1024 * 1024 * 1024;
初始化表达式中的4个常量都是int
类型,所有三个乘法都是int
- by - int
。结果恰好比2 31 更大(1.5倍),这意味着它不适合int
在int
所在的系统上32位。 int
结果,无论它是什么,都会被隐式转换为目标类型unsigned long
,但到那时为止已经太晚了;溢出已经发生。
溢出意味着您的代码具有未定义的行为(因为这可以在编译时确定,我希望您的编译器对其进行警告)。在实践中,签名溢出通常包含在内,因此上面的通常将foo
设置为-1073741824
。你不能指望它(而且它不是你想要的)。
理想的解决方案是通过首先确保所有内容都是目标类型来避免隐式转换:
unsigned long foo = 3UL * 1024UL * 1024UL * 1024UL;
(严格来说,只有第一个操作数必须是unsigned long
类型,但它更容易保持一致。)
让我们看看更一般的案例:
int a, b, c, d; /* assume these are initialized */
unsigned long foo = a * b * c * d;
您无法为变量添加UL
后缀。如果可能,您应该更改a
,b
,c
和d
的声明,以便它们属于unsigned long long
类型,但也许在那里&{ #39;其他一些原因,他们需要int
类型。您可以向添加强制转换将每个转换为正确的类型。通过使用强制转换,您可以准确控制转换的执行时间:
unsigned long foo = (unsigned long)a *
(unsigned long)b *
(unsigned long)d *
(unsigned long)d;
这有点冗长;您可以考虑将强制转换仅应用于最左侧的操作数(确保您了解表达式的解析方式之后)。
注意:这将不工作:
unsigned long foo = (unsigned long)(a * b * c * d);
强制转换将int
结果转换为unsigned long
,但仅在溢出已经发生之后。它只是明确指定了隐式执行的强制转换。
答案 2 :(得分:4)
带有后缀的积分文字是 int ,如果它们可以适合,在你的情况下3
和1024
绝对适合。这在C99标准部分草案6.4.4.1
整数常量中有所介绍,本节的引用可以在我对Are C macros implicitly cast?的回答中找到。
接下来我们有乘法,它在它的操作数上执行通常的算术转换转换,但因为它们都是 int ,其结果太大而不适合signed int导致溢出。这是未定义的行为,根据 5 部分说明:
如果在评估表达式期间发生异常情况(即,如果出现异常情况) 结果不是数学定义的,也不是在其可表示值的范围内 类型),行为未定义。
我们可以使用clang和-fsanitize=undefined
标志( see it live )凭经验发现这种未定义的行为,其中包含:
运行时错误:有符号整数溢出:3145728 * 1024无法以“int”类型表示
虽然在两个补充中,这将最终成为负数。解决此问题的一种方法是使用ul
后缀:
3ul * 1024ul * 1024ul * 1024ul
那么为什么转换为无符号值的负数会产生非常大的无符号值?这一点在6.3.1.3
有符号和无符号整数部分中介绍,其中包含:
否则,如果新类型是无符号的,则通过重复添加或转换该值 减去一个可以在新类型中表示的最大值 直到该值在新类型的范围内.49)
这基本上意味着将unsigned long max + 1
添加到负数中,从而产生非常大的无符号值。