主要修改如下澄清:
鉴于有人已经提到了 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;
它的地址由&value 给出。同时,一个4个8位字节的数组可以表示为:
uint8_t arr[4];
请注意,我使用无符号变体,因为这些字节并不真正代表数字本身,因此将它们解释为“有符号”是没有意义的。现在,指向array-of-4-uint8_t 的指针定义为:
uint8_t (*ptr)[4];
如果我将 32 位值的地址分配给这样的数组,我将能够单独索引每个字节,这意味着我将直接读取字节,避免任何烦人的移位和屏蔽操作!
uint8_t (*bytes)[4] = (void *) &value;
我需要转换指针 ("(void *)"),因为 我无法忍受那个抱怨的编译器 &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);
}
当value 是32700 时,在我的机器上打印:
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“逻辑”字节。