将较窄的数据类型存储为C语言中更广泛的数据类型的正确方法是什么?

Jas*_*son 18 c types

我目前正在修复C代码中的遗留错误.在修复这个bug的过程中,我存储了unsigned int一个unsigned long long.但令我惊讶的是,当我在64位版本上编译此代码时,数学停止工作GCC.我发现问题是,当我分配一个long longint值,然后我得到一个看起来像的数字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;
Run Code Online (Sandbox Code Playgroud)

Mik*_*vey 18

我想你必须告诉编译器右边的数字是无符号的.否则它认为它是一个普通的signed int,并且由于符号位被设置,它认为它是负的,然后它将它签名扩展到接收器中.

所以在右边做一些未签名的演员.

  • @ShafikYaghmour:在这种特殊情况下,"3*1024*1024*1024"的数学结果恰好适合于`unsigned int`而不是`int`(假设是32位`int`).更一般地说,如果表达式溢出,使操作数无符号并不总是有帮助(行为可以很好地定义,但它仍然可以环绕).考虑`unsigned long long foo = 1024U*1024U*1024U*1024U;`数学结果是2**40,它适合于`unsigned long long`而不是`unsigned long`. (5认同)

Kei*_*son 12

表达通常是独立评估的; 他们的结果不受他们出现的背景的影响.

像的整数常数1024是最小的int,long int,long long int在其中它的值将适合; 在特殊情况下1024总是如此int.

我假设这u_long是一个typedef unsigned long(虽然你long long在你的问题中也提到过).

所以给出:

unsigned long foo = 3 * 1024 * 1024 * 1024;
Run Code Online (Sandbox Code Playgroud)

初始化表达式中的4个常量都是类型int,并且所有三个乘法都是int-by- int.结果恰好比2 31更大(1.5倍),这意味着它不适合32位int系统int.该int结果,不管它是什么,会被隐式转换为目标类型unsigned long,但那时已经太晚了; 溢出已经发生.

溢出意味着您的代码具有未定义的行为(因为这可以在编译时确定,我希望您的编译器警告它).实际上,签名溢出通常包含在内,因此上面通常会设置foo-1073741824.你不能指望它(而且它不是你想要的).

理想的解决方案是通过首先确保所有内容都是目标类型来避免隐式转换:

unsigned long foo = 3UL * 1024UL * 1024UL * 1024UL;
Run Code Online (Sandbox Code Playgroud)

(严格来说,只有第一个操作数需要是类型unsigned long,但是更简单一致.)

让我们看看更一般的情况:

int a, b, c, d; /* assume these are initialized */
unsigned long foo = a * b * c * d;
Run Code Online (Sandbox Code Playgroud)

您无法UL为变量添加后缀.如果可能的话,你应该改变的声明a,b,c,和d所以他们类型的unsigned long long,但也许有他们需要类型的一些其他原因int.您可以添加强制转换以将每个转换显式转换为正确的类型.通过使用强制转换,您可以准确控制转换的执行时间:

unsigned long foo = (unsigned long)a *
                    (unsigned long)b *
                    (unsigned long)d *
                    (unsigned long)d;
Run Code Online (Sandbox Code Playgroud)

这有点冗长; 您可以考虑将强制转换仅应用于最左侧的操作数(在确保您了解表达式的解析方式之后).

注意:这将无法正常工作:

unsigned long foo = (unsigned long)(a * b * c * d);
Run Code Online (Sandbox Code Playgroud)

强制转换将int结果转换为unsigned long,但仅在溢出已经发生之后.它只是明确指定了隐式执行的强制转换.