C 中的 Memcpy - 在 for 循环中使用数组然后分配给结构

Memcpy in C - Using arrays in for loops then assigning to a struct

我正在使用 for 循环将数组从 UART RX 缓冲区复制到内存。

看起来像这样:

UART2_readTimeout(uartR2, rxBuf3, 54, NULL, 500);
GPIO_toggle(CONFIG_GPIO_LED_3);

if ((rxBuf3[4] == 0x8C) && (rxBuf3[10] != 0x8C)) {
    int i;
    for (i = 0; i < 47; i++) {
        sioR2[i]=rxBuf3[i];
    }

然后我想使用如下所示的 struct 以便在处理和组织数据时可以使用点符号:

typedef struct
{
  uint16_t voltage;
  uint16_t current;
  uint16_t outTemp;
  uint16_t inTemp;
  uint16_t status;
  uint32_t FaultA;
  uint32_t FaultB;
  uint32_t FaultC;
  uint32_t FaultD;
  uint8_t softwareMode;
  uint8_t logicLoad;
  uint8_t outputBits;
  uint16_t powerOut;
  uint32_t runHours;
  uint16_t unitAddresses[6];
} unitValues;

假设它们的总长度相同,是否可以对整个数组执行 memcpy 到结构的单个实例?

Array  : 001110101....110001
         |||||||||||||||||||   <- memcpy
         vvvvvvvvvvvvvvvvvvv
Struct : 001110101....110001

您必须将结构的打包对齐更改为 1 字节。

#pragma pack(1) /* change */
typedef struct {
...
}
#pragma pack() /* restore */

理论中,可以使用memcpy()从字节数组的元素中设置struct的成员字段。但是,您需要 非常小心 以防止您的编译器将 'empty' 字段添加到您的 struct(请参阅: Structure padding and packing) 除非在将数据加载到源数组时考虑这些空字段。 (源数组的元素打包到连续的内存中。)

不同的编译器使用不同的命令行 and/or #pragma 选项来控制结构打包,但是对于 MSVC 编译器,您可以在源代码中使用 #pragma pack(n) 指令或/Zp 命令行开关。

使用 MSVC 编译器,您提供的结构总大小为 47 个字节仅当您使用单字节打包时;对于默认打包,大小为 52 字节。

以下代码块显示了这些 'extra' 字节针对不同的打包大小将被插入的位置。

#pragma pack(push, 1) // This saves the current packing level then sets it to "n" (1, here)
typedef struct {
    uint16_t voltage;
    uint16_t current;
    uint16_t outTemp;
    uint16_t inTemp;
    uint16_t status;
    // 4+ byte packing will insert two bytes here
    uint32_t FaultA;
    uint32_t FaultB;
    uint32_t FaultC;
    uint32_t FaultD;
    uint8_t softwareMode;
    uint8_t logicLoad;
    uint8_t outputBits;
    // 2+ byte packing will insert one byte here
    uint16_t powerOut;
    // 4+ byte packing will insert two bytes here
    uint32_t runHours;
    uint16_t unitAddresses[6];
} unitValues;
#pragma pack(pop) // This restores the previous packing level

因此,sizeof(unitValues) 将是:

  • 使用 #pragma pack(1)
  • 时为 47 字节
  • 使用 #pragma pack(2)
  • 时为 48 字节
  • 使用 #pragma pack(4)(或任何 higher/default 值)时为 52 个字节

假设您的 C 实现提供了一种方法来确保您的结构布局与所讨论的驱动程序用于写入缓冲区的布局相同,一个很好的解决方法是驱动程序直接写入结构。我在这里推断驱动程序函数的签名,但这可能是这样的:

UART2_readTimeout(uartR2, (uint8_t *) &values, 54, NULL, 500);

假设 uint8_tunsigned char 或者 char 的别名,通过 uint8_t * 类型的指针写入结构的表示是有效的.因此,这避免了您必须制作副本。

然而,诀窍在于结构布局。假设您希望数据按照给定的顺序按照给定的结构成员进行布局,没有间隙,这样的结构布局将阻止结构实例在内存中定位,以便所有成员都在地址上对齐,这些地址是它们的倍数尺寸。根据您的硬件的对齐规则,这可能完全没问题,但可能会减慢对某些成员的访问速度,或者它会尝试访问某些成员使程序崩溃。

如果您仍想继续,那么您将需要查看编译器的文档以获取有关如何获得所需的结构布局的信息。您可能会查找对结构“包装”、结构布局或结构成员对齐的参考。没有执行此操作的标准方法——如果您的 C 实现完全支持它,那么这构成了一个扩展,具有特定于实现的细节。

所有相同的问题和注意事项都适用于使用 memcpy 将缓冲区内容复制到结构类型的实例上,因此如果您不需要数据的多个副本,您可以安排制作批量复制到结构上工作,那么你最好直接写入结构而不是写入一个单独的缓冲区然后复制。


另一方面,安全和标准的替代方法是允许您的实现按照它认为最好的方式布置结构,并将数据从缓冲区复制到逐个成员的结构中时尚,每个成员 memcpy()s。是的,代码会有点乏味,但它不会对对齐相关的问题敏感,甚至不会对结构成员的重新排序或添加新成员敏感。