我正在尝试将来自设备的输入(始终为 1 到 600000 之间的整数)转换为四个 8 位整数。
例如,
如果输入是 32700,我想要188 127 00 00.
我通过使用实现了这一点:
32700 % 256
32700 / 256
Run Code Online (Sandbox Code Playgroud)
以上工作到 32700。从 32800 开始,我开始得到不正确的转换。
我对此完全陌生,并希望得到一些帮助以了解如何正确完成此操作。
主要编辑以下澄清:
鉴于有人已经提到了 shift-and-mask 方法(不可否认,这是正确的方法),我将给出另一种方法,迂腐,不可移植,依赖于机器,并且可能表现出未定义的行为。尽管如此,IMO 还是一个很好的学习练习。
由于各种原因,您的计算机将整数表示为一组 8 位值(称为bytes);请注意,尽管极为常见,但情况并非总是如此(请参阅 参考资料CHAR_BIT)。出于这个原因,使用多于 8 位表示的值使用多个字节(因此使用多个位的值是 8 的倍数)。对于 32 位值,您使用 4 个字节,并且在内存中,这些字节始终紧随其后。
我们称指针为包含另一个值在内存中的地址的值。在这种情况下,字节被定义为指针可以引用的最小(就位计数而言)值。例如,您的 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
|===================
Run Code Online (Sandbox Code Playgroud)
因此,您想要做的(将 32 位字拆分为 8 位字)已经由您的计算机完成,因为它是由其处理器和/或内存架构强加给它的。为了获得这种几乎巧合的好处,我们将找到您的 32 位值的存储位置并逐字节读取其内存(而不是一次 32 位)。
由于所有严肃的 SO 答案似乎都这样做,让我引用标准 (ISO/IEC 9899:2018, 6.2.5-20) 来定义我需要的最后一件事(强调我的):
可以从对象和函数类型构造任意数量的派生类型,如下所示:
- 一个阵列型描述了一种连续与特定的成员对象类型分配非空的对象集,被称为元素类型。[...] 数组类型的特征在于它们的元素类型和数组中的元素数量。[...]
- [...]
因此,由于数组中的元素被定义为连续的,内存中的 32 位值,在具有 8 位字节的机器上,在其机器表示中,实际上只不过是一个 4 字节的数组!
给定一个 32 位有符号值:
int32_t value;
Run Code Online (Sandbox Code Playgroud)
它的地址由 给出&value。同时,一个 4 个 8 位字节的数组可以表示为:
uint8_t arr[4];
Run Code Online (Sandbox Code Playgroud)
请注意,我使用了 unsigned 变体,因为这些字节本身并不真正代表一个数字,因此将它们解释为“有符号”是没有意义的。现在,指向 4 数组的指针uint8_t定义为:
uint8_t (*ptr)[4];
Run Code Online (Sandbox Code Playgroud)
如果我将 32 位值的地址分配给这样的数组,我将能够单独索引每个字节,这意味着我将直接读取字节,避免任何讨厌的移位和屏蔽操作!
uint8_t (*bytes)[4] = (void *) &value;
Run Code Online (Sandbox Code Playgroud)
我需要转换指针(“ (void *)”),因为我无法忍受抱怨编译器 &value的类型是“指向- int32_t”,而我将它分配给“指向数组的 4- 指针uint8_t”和这种类型不匹配被编译器捕获并被标准警告;这是第一个警告,我们正在做的事情并不理想!
最后,我们可以访问每一个字节直接从内存中读取它通过索引:(*bytes)[n]读取n的字节第value!
总而言之,给定一个send_can(uint8_t)函数:
for (size_t i = 0; i < sizeof(*bytes); i++)
send_can((*bytes)[i]);
Run Code Online (Sandbox Code Playgroud)
并且,为了测试目的,我们定义:
void send_can(uint8_t b)
{
printf("%hhu\n", b);
}
Run Code Online (Sandbox Code Playgroud)
在我的机器上打印的时间value是32700:
188
127
0
0
Run Code Online (Sandbox Code Playgroud)
最后,这显示了此方法依赖于平台的另一个原因:存储 32 位字的字节的顺序并不总是您从二进制表示的理论讨论中所期望的,即:
实际上,AFAIK,C 语言允许对这 4 个字节进行排序的 24 种可能性中的任何一种(这称为endianness)。同时,移位和屏蔽将始终为您提供第n-th 个“逻辑”字节。