整数算术产生一个奇怪的结果(除法后舍入?)

时间:2016-02-23 13:43:17

标签: c

在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)。

  1. 结果是什么原因还是错误?
  2. 其他编译器的结果是什么?

5 个答案:

答案 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
  • e.g。使用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[^]处自行编译。