将32位数字转换为四个8位数字

3

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

例如,

如果输入为32700,则希望得到188 127 00 00

我通过使用以下方法实现了这一点:

32700 % 256 
32700 / 256 

以上代码可正常工作直到32700。从32800开始,我收到了错误的转换结果。

我是完全新手,希望得到一些帮助,了解如何正确地完成此操作。


你确定它真的是“从32800开始”吗?还是实际上是从32768开始的? - PiCTo
1
你说得对!!我猜你看到了问题.....告诉我,告诉我... - Aman Kejriwal
1
这个 % 是打错了吗?我期望 MOD 256 可以得到 188,而 /256 可以得到 127。令人困惑的是,% 是 C 运算符取模的意思,你肯定是指 MOD。也就是说,在我看来,你展示的两行代码在语义上是相同的。请展示一下你得到的两行代码的结果。哪一个可以得到所需的 188,哪一个可以得到 127? - Yunnosch
2
  1. 转换为 32 位有符号整数。我不明白。数字 32700 本身就是一个 32 位有符号整数。输入是 32700,我想要 188 127 00 00,即将有符号的 32 位整数转换为四个 8 位整数。我通过使用以下方法实现了这一点: 我不明白,那只是两个数字。我开始得到错误的转换结果 如果您在处理一些实际代码时遇到问题,请展示代码,而不是解释。请发布一个 [MCVE]。代码胜过千言万语。您的描述可能与您面临的问题完全无关。
- KamilCuk
1
我开始出现错误的转换。您使用什么架构、编译器、编译器版本和编译器选项?您是如何检查的?您在哪个操作系统/环境中执行程序?您是如何“观察”到发生了错误的转换?通过终端输出,打印机上打印的内容,还是使用调试器读取值?如何在SO上提出一个好问题?我认为这个问题相关:将int转换为4字节。 - KamilCuk
显示剩余9条评论
6个回答

5

根据澄清后的说明,进行了重大编辑:

鉴于已经有人提到了移位和掩码方法(无可否认地是正确的方法),我将给出另一种方法,严谨地说,它不具有可移植性、机器相关性,并且可能表现出未定义的行为。但在我看来它仍是一个很好的学习练习。

由于各种原因,您的计算机将整数表示为8位值的组(称为字节);请注意,尽管极其常见,但这并不总是这种情况(参见CHAR_BIT)。因此,使用超过8位的值使用多个字节(因此使用8的倍数的位数)。对于32位值,您使用4个字节,在内存中,这些字节始终紧随其后。

我们将一个包含另一个值内存地址的值称为指针。在这种情况下,一个字节被定义为可以由指针引用的最小值(就比特数而言)。例如,覆盖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)来定义我需要的最后一件事(重点在于我的):
任何数量的派生类型都可以从对象和函数类型构造,如下所示:
- 数组类型描述了具有特定成员对象类型(称为元素类型)的非空对象连续分配集合。[...]数组类型由它们的元素类型和数组中的元素数量所特征化。[...] - [...]
因此,由于数组中的元素定义为是连续的,在具有8位字节的机器上,内存中的32位值实际上在其机器表示中仅是由4个字节组成的数组!
给定一个32位有符号值:
int32_t value;

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

uint8_t arr[4];

请注意,我使用了无符号变量,因为这些字节并不是真正表示一个数字,因此把它们解释为“有符号”的是没有意义的。现在,指向4个uint8_t数组的指针被定义为:
uint8_t (*ptr)[4];

如果我将我们32位值的地址分配给这样一个数组,我就能够逐个索引每个字节,这意味着我将直接读取每个字节,避免任何繁琐的移位和掩码操作!

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

我需要强制转换指针 ("(void *)"),因为&value的类型是"指向int32_t的指针",而我正在将其分配给"指向4个uint8_t数组的指针",这种类型不匹配被编译器捕获并受到标准的严格警告; 这是第一个警告,说明我们正在做的事情并不理想!

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

将所有内容结合起来,假设有一个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位字节的顺序并不总是符合二进制表示的理论讨论(即字节排序),例如:
  • byte 0 包含31-24位
  • byte 1 包含23-16位
  • byte 2 包含15-8位
  • byte 3 包含7-0位
事实上,据我所知,C语言允许对这4个字节的顺序进行任何24种可能的排序(这被称为"字节序")。同时,移位和掩码运算将始终获得你想要的第n个“逻辑”字节。

我从基于设备位置的线性传感器中获取数值作为数字。 - Aman Kejriwal
我的任务是将这个数字转换为32位有符号整数表示形式,并使用CAN消息发送到显示单元。 - Aman Kejriwal
所以你需要将一个32位数字分成8位“数据包”?我不熟悉CAN协议...这可以解释模数。小心字节序! - PiCTo
我必须使用小端序。 - Aman Kejriwal
问题在于这不是我的主要领域,我只是为了孩子的遥控飞机控制器而做这件事:D - Aman Kejriwal

4
这真的取决于您的架构如何存储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等,因此您可以实际指定数字的位数,而不必担心int和long。
话虽如此,您希望将数字转换为4个8位int。你可以像这样做:
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 等的赋值。


1
“更快”这个评论并不准确;编译器应该选择最佳选项,而右移操作本质上就是除以二。 - M.M
我最近没有检查编译器生成的代码。我看过的那些都经历了整个模数/除法过程,而不是移位,但那是在15年前了。代码生成器可能已经有所改进。 - cup
@cup为什么要明确指出60,000是有符号长整型,当它被存储在无符号长整型变量中时? - abetancort

0

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

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];

0

您可以使用一些位掩码。

600000 是 0x927C0

600000 / (256 * 256) 可以得到 9,还没有进行掩码操作。
((600000 / 256) & (255 * 256)) >> 8 可以得到 0x27 == 39。使用一个由 8 个设置为 1 的位组成的 8 位移位掩码 (256 * 255) 并右移 8 位,即 >> 8,也可以使用另一个 / 256
600000 % 256 可以得到 0xC0 == 192,就像您所做的那样。掩码操作将是 600000 & 255


0
我最终做了这个:
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);

1
请使用 uint32_t 替代 unsigned long - Antti Haapala -- Слава Україні

-1

16位有符号整数可以存储的最大正值为32767。如果你强制使用比这更大的数字,你将得到一个负数作为结果,因此%/返回的值是意外的。

使用无符号16位整数可以获得范围高达65535,或者使用32位整数类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接