【问题标题】:Copy a uint8_t array to a struct将 uint8_t 数组复制到结构
【发布时间】:2019-01-22 14:16:25
【问题描述】:

我有这个数组

uint8_t *buffer = "JOHN:DOE:010119:M:FOO:BAR";

我想将它逐个字段复制到数据结构中

typedef struct{
  uint8_t firstName[5];
  uint8_t pad1;
  uint8_t lastName[4];
  uint8_t pad2;
  uint8_t dateOfBirth[7];
  uint8_t pad3;
  uint8_t genre;
  uint8_t pad4;
  uint8_t car[4];
  uint8_t pad5;
  uint8_t phone[4];
  uint8_t pad6;
}DataStructTypeDef;

假设所有长度都是固定的(例如,firstName 始终由 4 个字符组成,lastName 由 3 个字符组成,等等......)

我使用了这种方法:

DataStructTypeDef foo;
memcpy((void *)&foo, (void *)buffer, sizeof(DataStructTypeDef));

当我尝试打印 dateOfBirth 时,它会像这样显示从 01012019 开始的整个数组

int main(void)
{
  DataStructTypeDef foo;
  memcpy((void *)&foo, (void *)buffer, sizeof(DataStructTypeDef));
  printf("%s", foo.dateOfBirth); // It prints 010119:M:FOO:BAR
//printf("%s", foo.dateOfBirth); // Expected value 010119
  return 0;
}

【问题讨论】:

  • 警告:“firstName 总是由 4 个字符组成,lastName 是 3 个等等......”是假的,你错过了以空字符结尾的地方
  • @zerocoldTUN 如果你不以空字符结束字符串,你将无法使用假设存在的标准函数,所以没有 printf / strcpy / strdup/ .... @ 987654328@ 表示 lastName 包含 1 或 2 个字符,然后是 \0,而不是 3 个字符
  • 您的问题是什么?你知道 C 是如何模仿“字符串”的吗,实际上它不是 C 中的数据类型。你可能想了解 0-terminated char-arrays 的概念。
  • "using a padding bit" 你所说的“bit”实际上是byte,或者更准确地说是@ 987654332@.
  • 注意:此代码在 C 语言中不需要强制转换,建议使用 sizeof 对象。简化:memcpy(&foo, buffer, sizeof foo); 易于正确编码、审查和维护。

标签: c memcpy string-literals


【解决方案1】:

由于您要复制的 char array 成员不是以 null 结尾的,printf("%s", 将不知道它何时遇到每个字符串的结尾。

这可以在printf 中通过限制打印的字符数量来控制...

例如:

printf("%.*s", (int)sizeof(foo.dateOfBirth), foo.dateOfBirth);

等价物是:

printf("%.6s", food.dateOfBirth);

.* 指定要打印的字符的“精度”。所以在你的情况下,dateOfBirth = 精度/大小 6。

【讨论】:

  • %.*s 指定要输出的最大个字符。如果在此限制之前存在一个空终止符,则输出将在空终止符处停止。
【解决方案2】:
  1. 修改结构

为每个字段添加一个额外的字节以容纳“\0”字符。例如使用

uint8_t firstName[5];

而不是

uint8_t firstName[4];
  1. 单独解析字段并以'\0'结束每个字段

与其一次性复制整个缓冲区,不如一个一个地复制元素。由于每个字段的大小是固定的,因此从缓冲区开始的偏移量是固定的,这使得解析工作更容易。

【讨论】:

    【解决方案3】:

    带有固定结构

    typedef struct {
        uint8_t firstName[4];
        uint8_t pad1;
        uint8_t lastName[3];
        uint8_t pad2;
        uint8_t dateOfBirth[6];
        uint8_t pad3;
        uint8_t genre;
        uint8_t pad4;
        uint8_t car[3];
        uint8_t pad5;
        uint8_t phone[3];
        uint8_t pad6;
    }DataStructTypeDef;
    

    这对我有用:

    int main(void)
    {
        uint8_t *buffer = "JOHN" "\0" "DOE" "\0" "010119" "\0" "M" "\0" "FOO" "\0" "BAR";
        DataStructTypeDef foo;
        memcpy((void *)&foo, (void *)buffer, sizeof(DataStructTypeDef));
        printf("%s", foo.dateOfBirth); // Expected value 01012019
    }
    

    缓冲区看起来非常糟糕,因为如果我将"\0" "010119" 设置为"\0010119",它会以错误的方式解释转义。更好的解决方案可能是将其保留为一个并将八进制序列完全写为\000

    uint8_t *buffer = "JOHN\000DOE\000010119\000M\000FOO\000BAR";
    

    这里,每个\000 都成为一个空字节,并且它不会与跟随其中一个转义序列的010119 发生冲突。

    或者,如果我采用 "JOHN:DOE:010119:M:FOO:BAR" 的原始缓冲区字符串并在复制后替换所有 : ,则它可以工作,如下所示:

    foo.pad1 = foo.pad2 = foo.pad3 = foo.pad4 = foo.pad5 = foo.pad6 = '\0';
    

    【讨论】:

    • 你真的想用 string "\0" 初始化标量 uint8_t pad1 字段吗?
    • @AndrewHenle 不,这些字符串在编译时会自动连接成一个字符串。所以所有这些"\0" 都转换为一个\0。我之所以这样做,是因为否则当文字中的 0 紧跟在 \0 之后时,它会将其解释为不同的文字。
    • 啊。知道了。我没有发现字段之间缺少的,
    • 关于“它以错误的方式解释转义”:在 C 中,八进制转义序列具有一到三个八进制数字。编译器将”\001" 正确解释为1。可以用”\000" 指定零。
    • @alk:不,只有最后的“\0”会转换为 2 个空字节。连接的字符串没有额外的嵌入空字节,只有那些显式出现的。
    【解决方案4】:

    memcpy 之后添加:foo.pad1 = foo.pad2 = foo.pad3 = foo.pad4 = foo.pad5 = 0;。但我希望这是练习,而不是真正的工作结构。

    【讨论】:

    • “我希望这是练习,而不是真正的工作结构。”它有什么问题?
    • @zerocoldTUN 例如:1) 您是否希望只使用名字始终为 4 个字母而姓氏始终为 3 个字母的名称? 2) 代替pad1pad2,您可以扩展firstNamelastName,用1 个字节作为尾随零。 3) 名称使用char数组是正常的,不是字节数组。
    • 事实上这只是一个例子。我不处理名字和姓氏。事实上,我有一个 EEPROM(内存),我用它来存储我的产品的数据。产品 ID、制造日期、批号等......从内存中读取数据会返回一个字符数组,而我是定义这些字段的人。例如,我为每个数据使用固定长度字段:日期始终为 DDMMYY 和 1 月,1 日不是这样写的:1119。它始终是 010119。所以是的:我 100% 确定所有字段大小都是固定的,那就是为什么我想将它直接存储到数据结构中。
    • @zerocoldTUN: "100% 确定所有字段大小都是固定的" 但是,数据中的冒号 (:) 有什么用处?
    • @zerocoldTUN: '0'\0 相同。
    猜你喜欢
    • 2015-01-06
    • 1970-01-01
    • 2023-02-09
    • 1970-01-01
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    • 2014-12-13
    • 1970-01-01
    相关资源
    最近更新 更多