gpt4 book ai didi

c - 使用强制转换来访问像结构一样的字节数组?

转载 作者:太空狗 更新时间:2023-10-29 16:01:44 24 4
gpt4 key购买 nike

我正在从事基于微 Controller 的软件项目。该项目的一部分是二进制协议(protocol)的解析器。协议(protocol)是固定的,不能更改。PC 充当“主机”,主要传输命令,这些命令必须由“从机”(微 Controller 板)执行。

协议(protocol)数据由硬件通信接口(interface)接收,例如UART、CAN 或以太网。这不是问题。

接收到帧的所有字节(4 - 10,取决于命令)后,它们将存储在“uint8_t cmdBuffer[10]”类型的缓冲区中,并设置一个标志,表示现在可以执行命令执行。帧的第一个字节 (cmdBuffer[0]) 包含命令,帧的其余部分是命令的参数,数量和大小可能会有所不同,具体取决于命令。这意味着,可以通过多种方式解释有效负载。对于每个可能的命令,数据字节都会改变它们的含义。

我不想有太多丑陋的位操作,而是 self 记录的代码。所以我的做法是:

  • 我为每个命令创建一个“typedef struct”
  • 确定命令后,指向 cmdBuffer 的指针被转换为我的新 typedef 的指针
  • 通过这样做,我可以将命令的参数作为结构成员访问,避免数组访问中的魔数(Magic Number),参数的位操作 > 8 位,并且更易于阅读

例子:

typedef struct
{
uint8_t commandCode;
uint8_t parameter_1;
uint32_t anotherParameter;
uint16 oneMoreParameter;
}payloadA_t;

//typedefs for payloadB_t and payloadC_t, which may have different parameters

void parseProtocolData(uint8_t *data, uint8_t length)
{
uint8_t commandToExecute;

//this, in fact, just returns data[0]
commandToExecute = getPayloadType(data, length);

if (commandToExecute == COMMAND_A)
{
executeCommand_A( (payloadA_t *) data);
}
else if (commandToExecute == COMMAND_B)
{
executeCommand_B( (payloadB_t *) data);
}
else if (commandToExecute == COMMAND_C)
{
executeCommand_C( (payloadC_t *) data);
}
else
{
//error, unknown command
}
}

我看到了两个问题:

  • 首先,根据微 Controller 架构,2 字节或 4 字节参数的字节顺序可能是 intel 或 motorola。这个应该问题不大。该协议(protocol)本身使用网络字节顺序。在目标 Controller 上,可以使用宏来纠正顺序。

  • 主要问题:我的 tyepdef 结构中可能有填充字节。我正在使用 gcc,所以我可以在我的 typedef 中添加一个“packed”属性。其他编译器为此提供了 pragma。然而,在 32 位机器上,压缩结构将导致更大(和更慢)的机器代码。好的,这可能也不是问题。但我听说,访问未对齐的内存时可能会出现硬件故障(例如在 ARM 架构上)。

有很多命令(大约 50 个),所以我不想以数组形式访问 cmdBuffer我认为“结构方法”与“数组方法”相比提高了代码的可读性

所以我的问题:

  • 这种方法是否可行,还是只是一种肮脏的黑客攻击?
  • 是否存在编译器可以依赖“严格别名规则”并使我的方法不起作用的情况?
  • 有更好的解决方案吗?你会如何解决这个问题?
  • 这是否可以随身携带,至少可以携带一点?

问候,行李箱

最佳答案

通常,由于填充,结构对于存储数据协议(protocol)是危险的。对于可移植代码,您可能希望避免使用它们。因此,保留原始数据数组仍然是最好的主意。您只需要一种方法来根据收到的命令对其进行不同的解释。

此场景是需要某种多态性的典型示例。不幸的是,C 没有内置的 OO 功能支持,因此您必须自己创建它。

执行此操作的最佳方法取决于这些不同类型数据的性质。由于我不知道,所以我只能以这种方式提出建议,对于您的具体情况,它可能是最佳选择,也可能不是最佳选择:

typedef enum
{
COMMAND_THIS,
COMMAND_THAT,
... // all 50 commands

COMMANDS_N // a constant which is equal to the number of commands
} cmd_index_t;


typedef struct
{
uint8_t command; // the original command, can be anything
cmd_index_t index; // a number 0 to 49
uint8_t data [MAX]; // the raw data
} cmd_t;

第一步是在收到命令后,将其转换为相应的索引。

// ...receive data and place it in cmdBuffer[10], then:
cmd_t cmd;
cmd_create(&cmd, cmdBuffer[0], &cmdBuffer[1]);

...

void cmd_create (cmd_t* cmd, uint8_t command, uint8_t* data)
{
cmd->command = command;
memcpy(cmd->data, data, MAX);

switch(command)
{
case THIS: cmd->index = COMMAND_THIS; break;
case THAT: cmd->index = COMMAND_THAT; break;
...
}
}

一旦你有了一个从 0 到 N 的索引,就意味着你可以实现查找表。每个这样的查找表都可以是一个函数指针数组,它决定了对数据的具体解释。例如:

typedef void (*interpreter_func_t)(uint8_t* data);

const interpreter_func_t interpret [COMMANDS_N] =
{
&interpret_this_command,
&interpret_that_command,
...
};

使用:

interpret[cmd->index] (cmd->data);

然后你可以为不同的任务制作相似的查找表。

   initialize [cmd->index] (cmd->data);
interpret [cmd->index] (cmd->data);
repackage [cmd->index] (cmd->data);
do_stuff [cmd->index] (cmd->data);
...

针对不同的架构使用不同的查找表。字节序之类的事情可以在解释器函数中处理。你当然可以更改函数原型(prototype),也许你需要返回一些东西或传递更多参数等。

请注意,当所有命令导致相同类型的操作时,上面的示例最合适。如果您需要根据命令执行完全不同的操作,则其他方法更合适。

关于c - 使用强制转换来访问像结构一样的字节数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23285426/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com