【问题标题】:Converting 32 bit number to four 8bit numbers将 32 位数字转换为四个 8 位数字
【发布时间】:2020-10-17 18:45:58
【问题描述】:

我正在尝试将来自设备的输入(始终为 1 到 600000 之间的整数)转换为四个 8 位整数。

例如,

如果输入是32700,我要188 127 00 00

我通过以下方式实现了这一点:

32700 % 256 
32700 / 256 

以上工作直到 32700。从 32800 开始,我开始得到不正确的转换。

我对此完全陌生,希望得到一些帮助以了解如何正确完成此操作。

【问题讨论】:

  • 你确定它真的是从 32800 开始还是真的是从 32768 开始?
  • 你是对的!!我想你看到了这个问题.....告诉我告诉我......
  • % 是错字吗?我希望MOD 256 为您提供188 和/256 为您提供127。令人困惑的是% 是取模的C 运算符,您肯定指的是MOD。 IE。对我来说,你展示的两行在语义上是相同的。请显示您为这两行获得的结果。哪个给你想要的 188,哪个 127?
  • 600000) to a 32 bit signed integer. 我不明白。一个数字 32700 本身已经是一个 32 位有符号整数。 input is 32700, I want 188 127 00 00 将带符号的 32 位整数转换为四个 8 位整数。 I achieved this by using:我不明白,那只是两个数字。 I start getting incorrect conversions 如果您对某些实际代码有疑问,请显示代码,而不是解释。请发布minimal reproducible example。代码说 1000 个单词。您的描述可能与您面临的问题完全无关。
  • I start getting incorrect conversions 您使用的是什么架构、编译器、编译器版本和编译器选项?你是怎么检查的?您在什么操作系统/环境上执行您的程序?您如何“观察”到发生了不正确的转换?使用终端输出,在您的打印机上打印一些东西,您使用调试器读取了一个值? How do we ask a good question on SO。我认为相关:converting an int to 4 byte

标签: c bit 32-bit


【解决方案1】:

主要修改如下澄清:

鉴于有人已经提到了 shift-and-mask 方法(不可否认,这是正确的方法),我将提供另一种方法,它是迂腐的,不便携,不依赖于机器,并且可能表现出未定义行为。尽管如此,IMO 还是一个很好的学习练习。

由于各种原因,您的计算机将整数表示为一组 8 位值(称为 字节);请注意,尽管极为常见,但并非总是如此(请参阅CHAR_BIT)。出于这个原因,使用超过 8 位表示的值使用多个字节(因此使用多个位的值是 8 的倍数)。对于 32 位值,您使用 4 个字节,并且在内存中,这些字节总是相互跟随。

我们称​​指针为一个值,该值包含另一个值在内存中的地址。在这种情况下,byte 被定义为可以被指针引用的最小(就位计数而言)值。例如,覆盖 4 个字节的 32 位值将有 4 个“可寻址”单元(每个字节一个),其地址被定义为这些地址中的第一个:

|==================|
| MEMORY | ADDRESS |
|========|=========|
|  ...   |   x-1   | <== Pointer to byte before
|--------|---------|
| BYTE 0 |    x    | <== Pointer to first byte (also pointer to 32-bit value)
|--------|---------|
| BYTE 1 |   x+1   | <== Pointer to second byte
|--------|---------|
| BYTE 2 |   x+2   | <== Pointer to third byte
|--------|---------|
| BYTE 3 |   x+3   | <== Pointer to fourth byte
|--------|---------|
|  ...   |   x+4   | <== Pointer to byte after
|===================

因此,您的计算机已经完成了您想要做的(将 32 位字拆分为 8 位字),因为它是由其处理器和/或内存架构强加给它的。为了获得这种几乎巧合的好处,我们将找到您的 32 位值的存储位置并逐字节读取其内存(而不是一次读取 32 位)。

由于所有严肃的 SO 答案似乎都是如此,让我引用标准(ISO/IEC 9899:2018, 6.2.5-20)来定义我最不需要的东西(强调我的):

可以从对象和函数类型构造任意数量的派生类型,如下所示:

  • 数组类型描述了一个连续分配的具有特定成员对象类型的非空对象集,称为元素类型。 [...] 数组类型的特征在于它们的元素类型和数组中的元素数量。 [...]
  • [...]

因此,由于数组中的元素被定义为连续的,内存中的 32 位值,在具有 8 位字节的机器上,在其机器表示中,实际上只不过是 4 字节的数组!

给定一个 32 位有符号值:

int32_t value;

它的地址由&amp;value 给出。同时,一个4个8位字节的数组可以表示为:

uint8_t arr[4];

请注意,我使用无符号变体,因为这些字节并不真正代表数字本身,因此将它们解释为“有符号”是没有意义的。现在,指向array-of-4-uint8_t 的指针定义为:

uint8_t (*ptr)[4];

如果我将 32 位值的地址分配给这样的数组,我将能够单独索引每个字节,这意味着我将直接读取字节,避免任何烦人的移位和屏蔽操作!

uint8_t (*bytes)[4] = (void *) &value;

我需要转换指针 ("(void *)"),因为 我无法忍受那个抱怨的编译器 &amp;value 的类型是 "pointer-to-int32_t" 而我'm 将其分配给“pointer-to-array-of-4-uint8_t”,并且这种类型不匹配被编译器捕获并被标准警告警告;这是第一次警告我们所做的并不理想!

最后,我们可以通过索引直接从内存中读取每个字节单独访问每个字节:(*bytes)[n] 读取n-value 的第一个字节!

总而言之,给定一个send_can(uint8_t) 函数:

for (size_t i = 0; i < sizeof(*bytes); i++)
    send_can((*bytes)[i]);

并且,为了测试目的,我们定义:

void send_can(uint8_t b)
{
    printf("%hhu\n", b);
}

value32700 时,在我的机器上打印:

188
127
0
0

最后,这说明了此方法依赖于平台的另一个原因:32 位字的字节存储顺序并非总是您对理论的期望二进制表示的讨论ie

  • 字节 0 包含位 31-24
  • 字节 1 包含位 23-16
  • 字节 2 包含位 15-8
  • 字节 3 包含位 7-0

实际上,AFAIK,C 语言允许 任何 24 种可能性对这 4 个字节进行排序(这称为 endianness)。同时,移位和屏蔽总是会得到n-th“逻辑”字节。

【讨论】:

  • 我根据设备位置从线性传感器获取数值作为数字。
  • 我的任务是把这个数字转换成 32 位有符号整数表示,然后使用 CAN 消息发送到显示单元
  • 所以您必须将 32 位数字拆分为 8 位“数据包”?我不熟悉 CAN 协议……不过,这可以解释模数。小心字节序!
  • 对,我得用 little-edien
  • 问题是这也不是我的主要领域,我只是为我孩子的 RC 飞机控制器做这件事:D
【解决方案2】:

这实际上取决于您的架构如何存储 int。例如

  1. 8 或 16 位系统 short=16, int=16, long=32
  2. 32 位系统,short=16,int=32,long=32
  3. 64 位系统,short=16,int=32,long=64

这不是一个硬性规定——您需要先检查您的架构。还有一个 long long 但有些编译器无法识别它,并且大小因架构而异。

一些编译器定义了 uint8_t 等,因此您可以实际指定数字的位数,而不必担心整数和长整数。

话虽如此,您希望将一个数字转换为 4 个 8 位整数。你可以有类似的东西

unsigned long x = 600000UL;  // you need UL to indicate it is unsigned long
unsigned int b1 = (unsigned int)(x & 0xff);
unsigned int b2 = (unsigned int)(x >> 8) & 0xff;
unsigned int b3 = (unsigned int)(x >> 16) & 0xff;
unsigned int b4 = (unsigned int)(x >> 24);

使用移位比乘法、除法或取模快得多。这取决于您希望达到的字节顺序。您可以使用 b1 和 b4 等公式来反转分配。

【讨论】:

  • “更快”的评论不准确;编译器应该选择最好的选项,而右移定义为除以二
  • 我最近没有检查编译器生成的代码。我确实看过的那些经历了整个模/除法过程而不是转移,但那是 15 年前的事了。从那时起,代码生成器可能会继续发展。
  • @cup 为什么当 60.000 存储在 unsigned long 变量中时,你必须明确指出它是一个有符号长整数?
【解决方案3】:

你可以做一些位掩码。

600000 是 0x927C0

600000 / (256 * 256) 为您提供 9,还没有屏蔽。
((600000 / 256) &amp; (255 * 256)) &gt;&gt; 8 为您提供 0x27 == 39。使用 8 位 (256 * 255) 的 8 位移位掩码和 8 位右移, &gt;&gt; 8,这也是可能的,因为另一个 / 256
600000 % 256 会像你那样为你提供 0xC0 == 192。掩码为600000 &amp; 255

【讨论】:

    【解决方案4】:

    我最终这样做了:

    unsigned char bytes[4];
    unsigned long n;
    
    n = (unsigned long) sensore1 * 100;
    
    bytes[0] = n & 0xFF;     
    bytes[1] = (n >> 8) & 0xFF;
    bytes[2] = (n >> 16) & 0xFF;
    bytes[3] = (n >> 24) & 0xFF;
           CAN_WRITE(0x7FD,8,01,sizeof(n),bytes[0],bytes[1],bytes[2],bytes[3],07,255);
    

    【讨论】:

    • 使用uint32_t 而不是unsigned long
    【解决方案5】:

    我在打包和解包要传输/接收的大量自定义数据包时遇到过类似的情况,我建议您尝试以下方法:

    typedef union 
    {
       uint32_t u4_input;
       uint8_t  u1_byte_arr[4];
    }UN_COMMON_32BIT_TO_4X8BIT_CONVERTER;
    
    UN_COMMON_32BIT_TO_4X8BIT_CONVERTER un_t_mode_reg;
    un_t_mode_reg.u4_input = input;/*your 32 bit input*/
    // 1st byte = un_t_mode_reg.u1_byte_arr[0];
    // 2nd byte = un_t_mode_reg.u1_byte_arr[1];
    // 3rd byte = un_t_mode_reg.u1_byte_arr[2];
    // 4th byte = un_t_mode_reg.u1_byte_arr[3];
    

    【讨论】:

      【解决方案6】:

      您可以在 16 位有符号整数中存储的最大正值是 32767。如果您强制使用大于该值的数字,您将得到一个 数字,因此会出现意外值由%/ 返回。

      对于最大范围为 65535 的范围使用无符号 16 位 int 或 32 位整数类型。

      【讨论】:

        猜你喜欢
        • 2014-10-07
        • 2013-05-07
        • 2012-07-26
        • 2015-09-30
        • 2013-12-20
        • 2017-04-19
        • 2014-07-25
        • 2021-12-16
        • 2015-12-12
        相关资源
        最近更新 更多