在Linux上使用gcc版本4.8.4,short为16位,int为32位。
#include "stdio.h"
int main( void ){
unsigned short u = 0xaabb;
unsigned int v = 0xaabb;
printf ("%08x %08x\n", u, (unsigned short)((u*0x10001)/0x100));
printf ("%08x %08x\n", v, (unsigned short)((v*0x10001)/0x100));
return 0;
}
结果:
0000aabb 0000bbab
0000aabb 0000bbaa
这可以改变,例如,通过除以0x10,这对于第一种情况产生类似的结果(+1)。如果/0x100
截断的字节小于0x80,则不会产生这种效果。第一种情况(short u
)的机器代码看起来好像是要进行一些舍入(添加0xFF)。
答案 0 :(得分:14)
像0x10001
这样的文字将是int
类型(如果它可以放在int中,在这种情况下是正确的)。 int
是签名类型。
由于变量u
是一个小整数类型,因此只要在表达式中使用它,它就会被提升为int
。
0xaabb * 0x10001
应该会给出结果0xAABBAABB
。但是,该结果太大,无法容纳在32位二进制补码系统的int
内,其中int
的最大数字为0x7FFFFFFF
。因此,您会在有符号整数上获得溢出,从而调用未定义的行为 - 任何事情都可能发生。
在进行任何形式的二进制算术时,切勿使用有符号整数!
此外,最终转换为(unsigned short)
是徒劳的,因为printf参数无论如何都会将传递的值提升为int
。严格说来也是错误的,因为%x
表示printf
期望unsigned int
。
为避免C中不可预测且有限的默认整数类型出现问题,请改用stdint.h
。此外,使用unsigned int literals可以解决许多隐式类型提升错误。
示例:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main( void ){
uint16_t u = 0xaabb;
uint16_t v = 0xaabb;
printf ("%08" PRIx16 " %08" PRIx16 "\n", u, (uint16_t)(u*0x10001u/0x100u));
printf ("%08" PRIx16 " %08" PRIx16 "\n", v, (uint16_t)(v*0x10001u/0x100u));
return 0;
}
(此代码也会有参数提升,但是通过使用PRIx16
格式说明符,您告诉printf
现在编译器的业务是使代码工作,而不管是什么类型的促销可能存在于函数调用中。)
答案 1 :(得分:3)
正常的算术转换正在播放。
在乘法之前, u
被转换为int
。由于int
已签名,因此在分割时行为不同。
printf("%08x\n", (u*0x10001)/0x100);
printf("%08x\n", (v*0x10001)/0x100);
返回
ffaabbab
00aabbaa
严格来说,有符号整数上的乘法溢出已经是未定义的行为,因此即使在除法之前结果也是无效的。
答案 2 :(得分:1)
protected function postStatus(Illuminate\Http\Request $request)
{
$data = $request->all();
return Posts::create([
'user_name' => $data['user_name'],
'body' => $data['body'],
'photo' => $data['photo'],
'visibility' => $data['visibility'],
]);
}
的结果是u*0x10001
=导致int
类型溢出,从而导致未定义的行为。
答案 3 :(得分:1)
假设16位short
和32位int
(典型的x86,ARM和大多数其他32位系统):
您的代码中有两种类型的未定义行为(UB)。首先,在格式字符串中使用错误的类型说明符。 %x
需要unsigned int
,而unsigned short
已延长至signed int
。
第二个 - 你在这里看到的是第一个计算:u
被转换为int
(整数提升) - 而不是unsigned int
用于乘法,因为常量{{1也是0x10001
。乘法调用UB,因为它生成有符号整数溢出。一旦你调用UB,你就会迷失方向,任何进一步的解释都是无用的。
说,我们现在推测:发生的是,在乘法之后,你可能有一个负值,并且随着除法向零舍入(这是标准要求),你得到更高的负值。但是当你打印为无符号时,你会看到一个更大的原始(无符号)值。这是因为2的补充内部表示负值。
请注意,此结果超出了C标准。事实上,编译器可能会生成代码来格式化您的硬盘驱动器,或者您的计算机可能跳出窗口或nasal daemons可能出现。所以,纠正错误:
int
打印%hx
unsigned short int
强制转换为u * 0x10001U
进行乘法运算。通常,如果使用无符号值,建议始终使用unsigned int
(无符号)后缀。答案 4 :(得分:1)
我稍微扩展了你的代码来解释:
#include "stdio.h"
int main( void ){
unsigned short u = 0xaabb;
unsigned int v = 0xaabb;
printf ("not casted:\n");
printf ("%08x %08x\n", u, ((u*0x10001)/0x100));
printf ("%08x %08x\n", v, ((v*0x10001)/0x100));
printf ("unsigned short casted:\n");
printf ("%08x %08x\n", u, (unsigned short)((u*0x10001)/0x100));
printf ("%08x %08x\n", v, (unsigned short)((v*0x10001)/0x100));
printf ("u*0x10001:\n");
printf ("x=%08x d=%d\n", u*0x10001, u*0x10001);
// Solution
printf ("Solution:\n");
printf (">>> %08x %08x\n", u, (unsigned short)((u*0x10001UL)/0x100UL));
printf (">>> %08x %08x\n", v, (unsigned short)((v*0x10001UL)/0x100UL));
return 0;
}
这导致以下输出:
not casted:
0000aabb ffaabbab
0000aabb 00aabbaa
unsigned short casted:
0000aabb 0000bbab
0000aabb 0000bbaa
u*0x10001:
x=aabbaabb d=-1430541637
Solution:
>>> 0000aabb 0000bbaa
>>> 0000aabb 0000bbaa
所以您看到操作u*0x10001
将生成signed int
(32位)值,因此您的结果为d=-1430541637
。如果您将此值除以0x100
,您将获得0xFFAABBAB
的结果。如果您使用unsigned short
投射此值,则会得到结果= 0x0000BBAB
。如果要防止这种情况,编译器使用无符号值进行此操作,则必须将UL
写为数字的扩展名。
所以你看到编译器正在按预期工作。您可以在Code[^]处自行编译。