【问题标题】:right left shift bits in CC中的右左移位位
【发布时间】:2017-01-29 10:54:10
【问题描述】:

我对 C 中的位移做了一个小测试,所有位移 0、8、16 位都可以,我明白发生了什么。

但是我不清楚的 32 位右移或左移,我正在做测试的变量是 32 位长。

然后,我更改了保存移位结果的 32 位变量,但 32 位右左移位是一样的!

这是我的代码:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <inttypes.h>

int main() {
    uint32_t code = 0xCDBAFFEE;

    uint64_t bit32R = code >> 32;
    uint16_t bit16R = code >> 16;
    uint8_t bit8R = code >> 8;
    uint8_t bit0R = code >> 0;

    uint64_t bit32L = code << 32;
    uint16_t bit16L = code << 16;
    uint8_t bit8L = code << 8;
    uint8_t bit0L = code << 0;

    printf("Right shift:\nbit32R %.16x\nbit16R %x\nbit8R %x\nbit0R %x\n\n",
           bit32R, bit16R, bit8R, bit0R);
    printf("Left shift:\nbit32L %.16x\nbit16L %x\nbit8L %x\nbit0L %x\n\n",
           bit32L, bit16L, bit8L, bit0L);
}

这是我得到的结果:

Right shift:
bit32R 00000000cdbaffee
bit16R 0
bit8R cdba
bit0R ff

Left shift:
bit32L 00000000cdbaffee
bit16L 0
bit8L 0
bit0L 0

Process returned 61 (0x3D)   execution time : 0.041 s
Press any key to continue.

【问题讨论】:

  • 您的代码调用了未定义的行为。 32 的移位值对于 32 位 int 来说太大(如果您的平台有 16 位 int,则对于 16 位也是如此)。并使用来自inttypes.hPRInX 宏来打印固定宽度的类型。您对整数运算的工作原理有误解。
  • 我将包含文件更改为stdint!这并没有改变任何东西,但我忘记了 stdint,并包含了 inttypes。但我认为它们是相同的,至少对于我的代码而言。
  • 也许在做出假设之前,最好先阅读标题提供的内容、它们之间的关系以及PRInX 的含义?
  • 你能简单地告诉我 inttypes 和 stdint 提供什么吗?
  • 谷歌关闭了吗?

标签: c bit-manipulation


【解决方案1】:

将整数右移等于或大于其大小的位数是未定义的行为。

C11 6.5.7 移位运算符

语法

shift-expression: additive-expression
    shift-expression << additive-expression
    shift-expression >> additive-expression

约束

每个操作数都应该是整数类型。

语义

对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则行为未定义。

E1 &lt;&lt; E2 的结果是E1 左移E2 位位置;空出的位用零填充。如果E1 具有无符号类型,则结果的值为E1 × 2E2,比结果类型中可表示的最大值减一模。如果E1 具有带符号类型和非负值,并且E1 × 2E2 在结果类型中是可表示的,那么这就是结果值;否则,行为未定义。

E1 &gt;&gt; E2 的结果是E1 右移了E2 位位置。如果E1 具有无符号类型或E1 具有带符号类型和非负值,则结果的值是E1 / 2E2 商的整数部分。如果E1 具有带符号类型和负值,则结果值是实现定义的。

您平台上int 的大小似乎最多为32 位,因此bit32Rbit32L 的初始化程序具有未定义的行为。

64位表达式应该写成:

uint64_t bit32R = (uint64_t)code >> 32;

uint64_t bit32L = (uint64_t)code << 32;

此外,printf 中使用的格式对于传递的参数不正确(除非int 有 64 位,这会产生不同的输出)。

您的编译器似乎不完全符合 C99,您应该在 main() 函数体的末尾添加最终的 return 0; 语句。

这是一个更正的版本:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <inttypes.h>

int main(void) {
    uint32_t code = 0xCDBAFFEE;

    uint64_t bit32R = (uint64_t)code >> 32;
    uint16_t bit16R = code >> 16;
    uint8_t bit8R = code >> 8;
    uint8_t bit0R = code >> 0;

    uint64_t bit32L = (uint64_t)code << 32;
    uint16_t bit16L = code << 16;
    uint8_t bit8L = code << 8;
    uint8_t bit0L = code << 0;

    printf("Right shift:\n"
           "bit32R %.16"PRIx64"\n"
           "bit16R %"PRIx16"\n"
           "bit8R %"PRIx8"\n"
           "bit0R %"PRIx8"\n\n",
           bit32R, bit16R, bit8R, bit0R);

    printf("Left shift:\n"
           "bit32L %.16"PRIx64"\n"
           "bit16L %"PRIx16"\n"
           "bit8L %"PRIx8"\n"
           "bit0L %"PRIx8"\n\n",
           bit32L, bit16L, bit8L, bit0L);

    return 0;
}

输出是:

Right shift:
bit32R 0000000000000000
bit16R cdba
bit8R ff
bit0R ee

Left shift:
bit32L cdbaffee00000000
bit16L 0
bit8L 0
bit0L ee

这可能不是您所期望的,因为变量的类型有些不一致。

【讨论】:

  • return 0 for main() 在 C99 中从来都不是强制的,据说如果省略返回值 0 将由编译器提供(main() 函数的原型也是平台相关的,void main (无效)也可以接受)。目前,完全符合 C99 的编译器非常少,C++11 的编译器更少,尤其是在嵌入式平台上。我什至不确定他是否可以访问 PRIx 宏,我手头的编译器在标题中没有它们
  • @Swift:运行环境Process returned 61 (0x3D)打印的状态表明编译器确实没有添加了C99强制的隐式return 0;,因此我关于非兼容的编译器。如果这个环境基于 MSVC,我不会感到惊讶。在任何情况下,总是在main() 的末尾显式地返回 0(或一些有意义的退出状态)是一种很好的风格。 C99 隐式返回语义是对草率代码恕我直言的蹩脚修复。
  • 绝对!那工作得很好。但是为什么 (%.16x) 不适用于 64 可变大小?它显示 64 位十六进制大小,但移位不正确。使用 PRIx64,工作正常吗?
  • @PerchEagle: "%.16"PRIx64 需要 uint64_t 类型的值并打印至少 16 位的十六进制转换。如果你使用%.16x,你也会得到16个十六进制数字(其中8个必须是0),但预期的类型是unsigned int,这可能与uint64_t不兼容,因此你的printf格式有未定义的行为.如果类型 unsigned long long 有 64 位,PRIx16 可以扩展为 "llu"
  • 但是没有类型转换我得到相同的输出!
【解决方案2】:

一个问题是您使用%x 打印一个64 位整数。您应该为每个变量使用正确的格式说明符。有可用的宏:

#define __STDC_FORMAT_MACROS
#include <inttypes.h>

// ...

printf("64 bit result: %" PRIx64 "\n", bit32R);
printf("16 bit result: %" PRIx16 "\n", bit16R);
printf("8 bit result: %" PRIx8 "\n", bit8R);

更多信息可以在here找到。

【讨论】:

  • 虽然%x 是错误的,但使用 PRIx64 不会改变移位的结果。
  • 如果 PRIx64 没有改变结果,那为什么 %x 是错误的?你的修改我得到了同样的结果。我什至可以使用 %.16x 修饰符获得 64 位的十六进制完整格式。
  • @Perch 这些是特定于平台的东西。一般来说,它似乎可以工作,但是在其他平台上失败,或者在您编写更高级的代码时失败。我承认我在编写代码时不会费心使用所有这些宏,但如果你总是知道你将在哪个平台上运行,你应该跳过它们。否则,灾难就会发生。
  • 感谢您的提示,但无论如何,当我想编写代码时,我想如何知道这样的宏?喜欢这个“#define __STDC_FORMAT_MACROS”吗?我认为这与 inttypes.h 有关。
  • @PerchEagle:您应该知道使用 uint64_t 处理 64 位整数的方式相同。但我知道在某些代码中很容易看到int*_t 被使用并复制它,这样在尝试打印它们时很难发现您需要PRI* 宏。如果在 printf() 的手册页中提到了宏,那就太好了...
【解决方案3】:

你没有在那里做 64 位左移,因为代码是 uint32_t,所以编译器使用 32 位版本的运算符。此外,您应该告诉 print 使用 long long(与 uint64_t 相同)

#include <cstdint>
#include <stdio.h>
int main ()
{
uint32_t code = 0xCDBAFFEE;


uint64_t bit32R=((uint64_t)code)>>32;
uint16_t bit16R=code>>16;
uint8_t bit8R=code>>8;
uint8_t bit0R=code>>0;

uint64_t bit32L=((uint64_t)code)<<32;
uint16_t bit16L=code<<16;
uint8_t bit8L=code<<8;
uint8_t bit0L=code<<0;


printf("Right shift:\nbit32R %llx\nbit16R %x\nbit8R %x\nbit0R %x\n\n", bit32R,bit16R,bit8R,bit0R);
printf("Leftt shift:\nbit32L %llx\nbit16L %x\nbit8L %x\nbit0L %x", bit32L,bit16L,bit8L,bit0L);
}

结果是:

Right shift:
bit32R 0
bit16R cdba
bit8R ff
bit0R ee

Leftt shift:
bit32L cdbaffee00000000
bit16L 0
bit8L 0
bit0L ee

如果你有 C99 兼容的编译器,你应该使用 inttypes.h 中定义的宏,遗憾的是有些平台没有这些定义。 printf 的格式描述符取决于平台。

【讨论】:

  • OP 没有声称做 64 位。 64 位也不是所有平台上的long long
  • @Olaf Dietsche :这几乎就是索赔。 uint64_t bit32L=代码他的平台得到了 64 位的时间。
  • 但是有 uint64_t 可以为您提供 64 位变量。
  • @PerchEagle 是的,你得到一个 64 位的结果,但只有在产生未定义的行为之后。如果您想要一个可靠/定义的 64 位结果,您也必须使用 64 位参数进行计算。这就是斯威夫特建议演员阵容的原因。
猜你喜欢
  • 2022-01-23
  • 1970-01-01
  • 1970-01-01
  • 2017-01-07
  • 2014-12-30
  • 2015-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多