【问题标题】:Copy two elements from uint8_t array into uint16_t variable using pointers使用指针将 uint8_t 数组中的两个元素复制到 uint16_t 变量中
【发布时间】:2021-03-12 21:04:47
【问题描述】:

我有一个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 是否更可取?

它的最终用途是处理大端字节流,因此我将酌情使用byteorder(3) 函数。

【问题讨论】:

  • 有什么理由不想使用memcpy 版本吗?就避免未定义的行为而言,这是最安全的,我希望任何体面的优化器尽可能优化副本
  • 由于严格的别名规则,memcpy 被认为更可取。另一种选择是移位和或创建uint16_t。这也消除了字节顺序问题。
  • 没有特别的理由不想使用memcpy;这是我在 99% 的情况下使用的。我最初使用 shift 和 OR,但将其替换为 memcpy。然后我注意到memcpy 在链式函数调用中的链式使用,并且产生了疑问。我想我知道这些选项,我只是觉得我没有足够的经验来比较它们并评估我应该使用哪个和为什么。
  • 值得注意的是,将常量 2 视为 memcpy 调用的大小的编译器优化器几乎总是会重写以完全不涉及函数调用或循环,而只是使用寄存器操作字节.这样做的成本可以忽略不计,不用担心函数开销或缓存之外不必要的内存访问。

标签: arrays c pointers casting endianness


【解决方案1】:
uint8_t bytes[] = { 0x1A, 0x2B, 0x3C, 0x4D };
uint16_t word = 0;
memcpy(&word, &bytes[1], sizeof(word));

以上都不错。直接复制字节。如果您乐于按顺序复制字节并且不关心字节顺序,这就是这样做的方法。

请参阅P__J 支持波兰女性的答案,以证明编译器足够聪明,可以优化这种用法,因此您不必担心自己对其进行优化。

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

【讨论】:

  • 谢谢克里斯蒂安。回应您关于字节顺序的声明。这取决于我对数据的处理方式。对于我关心字节顺序的情况,我使用ntohs() 将字节复制到uint16_t 变量后进行转换。
  • 您能否进一步解释一下严格的对齐要求,以及我怎么会得到一个无效的地址?有什么方法可以让 gcc 警告我吗?还是演员们坚定地告诉它我很高兴自己在脚上开枪?
  • 更新了更多关于对齐和严格别名的内容。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2020-01-11
    • 1970-01-01
    • 2022-01-12
    • 1970-01-01
    • 2023-03-08
    • 2016-02-27
    • 2023-02-09
    • 2018-05-15
    • 1970-01-01
    相关资源
    最近更新 更多