使用指针将 uint8_t 数组中的两个元素复制到 uint16_t 变量中

Copy two elements from uint8_t array into uint16_t variable using pointers

我有一个 uint8_t 数组。我有时需要将两个顺序元素视为 uint16_t,并将它们复制到另一个 uint16_t 变量中。元素不一定从 uint16_t 字边界开始。

目前我正在使用 memcpy 这样做:

uint8_t bytes[] = { 0x1A, 0x2B, 0x3C, 0x4D };
uint16_t word = 0;
memcpy(&word, &bytes[1], sizeof(word));

gdb 显示这按预期工作:

(gdb) x/2bx &word
0x7fffffffe2e2: 0x2b    0x3c

将对数组元素的引用转换为 uint16_t 导致仅复制直接引用的数组元素:

word = (uint16_t)bytes[1];

(gdb) x/2bx &word
0x7fffffffe2e2: 0x2b    0x00

使用 uint16_t 指针并将其指向转换为 uint16_t * 的数组元素地址会导致引用两个顺序元素,但不会复制:

uint16_t *wordp = (uint16_t *)&bytes[1];

(gdb) x/2bx wordp
0x7fffffffe2e5: 0x2b    0x3c

bytes[1] = 0x5E;

(gdb) x/2bx wordp
0x7fffffffe2e5: 0x5e    0x3c

为常规 uint16_t 变量分配从 uint16_t 指针取消引用的值复制两个顺序数组元素:

word = *wordp;
bytes[1] = 0x6F;

(gdb) x/2bx wordp
0x7fffffffe2e5: 0x6f    0x3c
(gdb) x/2bx &word
0x7fffffffe2e2: 0x5e    0x3c

gcc 在使用 -Wall 选项时不产生警告。

如果没有中间指针,我怎样才能达到相同的结果?

使用上述指针或尝试在没有中间指针的情况下这样做是否有任何问题?

在某些情况下使用 memcpy 是否更可取?

它的最终用途是处理 Big Endian 字节流,所以我使用 byteorder(3) 适当的函数。

uint8_t bytes[] = { 0x1A, 0x2B, 0x3C, 0x4D };
uint16_t word = 0;
memcpy(&word, &bytes[1], sizeof(word));

以上说的不错。直接复制字节。如果您乐于按顺序复制字节而不关心字节顺序,这就是实现它的方法。

请参阅P__J supports women in Poland 的回答以证明编译器足够聪明可以优化此用法,因此您不必担心优化问题它自己。

word = (uint16_t)bytes[1];

以上只是将您的 8 位值零扩展为 16 位值(0x002B 而不是 0x2B);大概不是你想要的。

uint16_t *wordp = (uint16_t *)&bytes[1];
word = *wordp;

请不要执行以上操作。这会导致未定义的行为。 uint16_tuint8_t 具有更严格的对齐要求,并且您可以获得指针类型的无效地址。也就是说,作为双字节数据类型,它需要存在于二的倍数边界上的地址(因此 0x2、0x4、0x6 等),而 uint8_t 没有这样的限制并且可以存在在 0x1、0x2、0x3 等处。(有关对齐的更多信息,请参阅 C11 工作草案的第 6.2.8 节)

取消引用它时,您也通过取消引用类型不兼容的对象而违反了严格的别名规则。 (有关兼容类型的更多信息,请参阅 C11 工作草案的第 6.2.7 节)

memcpy 是最安全的方式。并且使用现代编译器效率最高,因为不会调用 memcpy。

示例(易变的事件优化):

#include <stdint.h>
#include <string.h>

int main()
{
    volatile uint8_t bytes[4];
    volatile uint16_t word = 0;
    memcpy(&word, &bytes[1], sizeof(word));
}

memcpy 被翻译成

        movzx   eax, WORD PTR [rsp-3]
        mov     WORD PTR [rsp-6], ax

https://godbolt.org/z/57n879