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

Converting 32 bit number to four 8bit numbers

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

例如,

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

我通过使用实现了这一点:

32700 % 256 
32700 / 256 

以上方法一直有效到 32700。从 32800 开始,我开始得到不正确的转换。

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

主要编辑以下说明:

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

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

我们称一个指针一个包含另一个值在内存中的地址的值。在这种情况下,byte 被定义为指针可以引用的最小值(就位计数而言)。例如,您的 32 位值,包含 4 个字节,将有 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 位字)已经由你的计算机完成了,因为它是由它的处理器 and/or 内存架构强加给它的。为了获得这种几乎巧合的好处,我们将找到您的 32 位值的存储位置并读取其内存 byte-by-byte(而不是一次读取 32 位).

由于所有严肃的 SO 答案似乎都这样做,所以让我引用标准(ISO/IEC 9899:2018,6.2.5-20)来定义我需要的最后一件事(强调我的):

Any number of derived types can be constructed from the object and function types, as follows:

  • An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. [...] Array types are characterized by their element type and by the number of elements in the array. [...]
  • [...]

因此,由于数组中的元素被定义为连续的,内存中的 32 位值在具有 8 位字节的机器上实际上仅是其机器表示中的 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 的类型是 "pointer -to-int32_t”,而我将它分配给“指向数组的指针 4-uint8_t”,这种类型不匹配被编译器捕获并被标准;这是我们正在做的事情并不理想的第一个警告!

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

把它们放在一起,给定一个 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 位字的字节存储顺序并不总是期望从二进制表示的理论讨论 i.e:

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

实际上,据我所知,C 语言允许 任何 24 种可能性来对这 4 个字节进行排序(这称为 endianness)。同时,移动和屏蔽总是会让你得到第 n 个“逻辑”字节。

你可以做一些位掩码。

600000 是 0x927C0

600000 / (256 * 256) 得到 9,还没有屏蔽。
((600000 / 256) & (255 * 256)) >> 8 得到 0x27 == 39。使用 8 个设置位的 8 位移位掩码 (256 * 255) 和右移 8 位,>> 8,这也可以作为另一个/ 256.
600000 % 256 为您提供 0xC0 == 192。屏蔽将是 600000 & 255.

您可以在 16 位有符号整数中存储的最大正值是 32767。如果您强制使用一个大于该值的数字,您将得到一个 负数 数字作为结果,因此 %/.

返回了意外的值

使用 unsigned 16 位整数(最大范围为 65535)或 32 位整数类型。

这实际上取决于您的体系结构如何存储 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);

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

我最终这样做了:

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

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

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