【问题标题】:Result of type cast and bitwise operation in C depends on the orderC 中类型转换和按位运算的结果取决于顺序
【发布时间】:2018-06-28 06:17:27
【问题描述】:

我试图在不使用头文件<limit.h> 的情况下打印int, char, short, long 的最小值。所以按位运算将是一个不错的选择。但奇怪的事情发生了。

声明

printf("The minimum of short: %d\n", ~(((unsigned short)~0) >> 1));

给我

The minimum of short: -32768

但是声明

printf("The minimum of short: %d\n", ~((~(unsigned short)0) >> 1));

给我

The minimum of short: 0

这种现象也出现在char。但它不会出现在long, int 中。为什么会这样?

值得一提的是,我使用 VS Code 作为我的编辑器。当我在语句中将光标移到unsigned char

printf("The minimum of char: %d\n", (short)~((~(unsigned char)0) >> 1));

它给了我一个提示(int) 0,而不是我预期的(unsigned char)0。为什么会这样?

【问题讨论】:

  • @Lundin 误解了这个问题。
  • 你为什么要避免limit.h - 这正是标准库中存在头文件的原因?!
  • @Andrew 我正在阅读 K&R2(C 编程语言)一书。打印int, long, short, char 的最大值和最小值是一个练习。但是,解决方案手册(The C Answer book)提供了两种解决方案,其中之一是使用limit.h。但另一个是关于按位运算。所以我产生了兴趣并试了一下。没想到这会是个猪圈。
  • 很公平 - 但请注意,K&R2 已有 20 年历史,因此不是现代 C 语言的理想教程(非常尊重 Messrs K&R)
  • @Andrew:也许这是一个误判,我不是 C 专家,但我认为阅读 K&R 并完成所有示例练习,让你有能力回答广大该站点上弹出的大多数 C 问题。对我来说,这说明了这本书。我还没有遇到过更彻底地传达指针算术、字符串库函数和复杂声明等主题的书。

标签: c visual-studio-code bitwise-operators implicit-conversion bit-shift


【解决方案1】:

首先,您的任何代码都不是真正可靠的,也不会达到您的预期。

printf 和所有其他可变参数长度函数都有一个功能失调的“功能”,称为默认参数提升。这意味着传递的参数的实际类型会进行静默提升。小整数类型(例如charshort)被提升为带符号的int。 (并且 float 被提升为 double。)Tl;dr:printf 是一个疯狂的函数。

因此你可以在各种小整数类型之间随意转换,最后还是会有升级到int。如果您为预期类型使用正确的格式说明符,这没有问题,但您没有使用%d,它是int

此外,~ 运算符与 C 中的大多数运算符一样,对其操作数执行隐式整数提升。见Implicit type promotion rules


话虽如此,这行~((~(unsigned short)0) >> 1) 执行以下操作:

  • 获取int 类型的文字0 并转换为unsigned short

  • 通过隐式整数提升将 unsigned short 隐式提升回 int

  • 计算int0 的按位补码。这是0xFF...FF hex,-1 dec,假设是 2 的补码。

  • 将此int 右移 1。在这里,您在移动负整数时调用实现定义的行为。 C允许这导致逻辑移位=移位零,或算术移位=符号位移位。编译器到编译器的结果不同且不可移植。

    在逻辑移位的情况下你会得到0x7F...FF,在算术移位的情况下你会得到0xFF...FF。在这种情况下,它似乎是后者,这意味着您在移位后仍然有十进制 -1

  • 您对0xFF...FF = -1 进行按位补码,得到0

  • 您将此投射到short。还是0

  • 默认参数提升将其转换为int。还是0

  • %d 需要 int,因此会相应地打印。 unsigned short%hu 打印,short%hd 打印。使用正确的格式说明符应该会取消默认参数提升的效果。

建议:研究隐式类型提升,避免在有符号类型的操作数上使用位运算符。

要简单地显示各种有符号类型的最低 2 的补码值,您必须对无符号类型进行一些技巧,因为对其有符号版本的按位运算是不可靠的。示例:

int shift = sizeof(short)*8 - 1;  // 15 bits on sane systems
short s = (short) (1u << shift);
printf("%hd\n", s);

这会将 unsigned int 1u 移动 15 位,然后以某种“实现定义的方式”将其结果转换为 short,这意味着在二进制补码系统上,您最终会将 0x8000 转换为 -32768。

然后给printf 提供正确的格式说明符,您将从那里得到预期的结果。

【讨论】:

  • @SanderDeDycker:这是一个很好的答案,但最终很脆弱。好好读,但不要想太多。事实仍然是,您无法在 C 中可移植地计算有符号整数类型的最小值。还要注意,在 2 的补码系统中,您会发现以 -MAX - 1 的形式定义的最小值避免不需要的类型提升。
  • 已修复。以安全便携的方式打印最小的十进制值确实有点棘手。
  • @Bathsheba 你可以便携地计算它,有点。我添加了一个例子。唯一不可移植的方面是从 unsigned int 到 short 的转换,这是实现定义的。但唯一由实现定义的是整数的大小,C 允许功能失调的有符号格式(如 1 的补码和有符号幅度),因此也可以填充位、陷阱和其他此类废话。但是考虑到 short 是 16 位并且系统是 2 的补码,上述代码的行为应该是确定性的。
  • @Lundin 谁说 char 是 8 位的? sizeof(short)*8 并不总是 short 的位数。 sizeof() 运算符以字符数返回大小,例如,某些系统具有 16 位字符。在这种情况下 sizeof(short) 最有可能返回 1。限制 CHAR_BIT 没有在limits.h中定义的方法,然后你也可以使用其余的定义。
  • @GoswinvonBrederlow 因此评论// 15 bits on sane systems。此代码不支持疯狂的系统,但请随时添加_Static_assert(CHAR_BIT==8, "Insane architectures not supported.");。担心移植到异国 DSP:s 的人只是在浪费每个人的时间。这有点像建造一个停车场,并考虑到一辆巨型卡车可能想停在那里。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 2013-07-22
  • 2021-12-02
相关资源
最近更新 更多