我需要一种 C 语言方法,能够处理各种格式/类型的数据包。您推荐的方法是什么(请注意,我希望尽可能符合 MISRA 要求)。目前,我正在考虑以下选项(但愿意学习其他选项),按照下面的代码
选项1:方法明确说明数据包的格式(并且方法内部有一个switch case) 选项 2:数据包括数据类型和与内容的联合
您更喜欢以下哪个选项?
非常感谢您的见解
int process_type1(const struct type1* data) {
/* do stuff with data */
}
int process_type2(const struct type2* data) {
/* do stuff with data */
}
/* Option 1: explicit data type in method to process data */
int process_1(const enum data_type data_type, const uint8_t* data, const size_t* n_bytes) {
int res = -1;
switch (data_type) {
case TYPE_1: res = process_type1((const struct type1*)data); break;
case TYPE_2: res = process_type2((const struct type2*)data); break;
default:
break;
}
return res;
}
/* Option 2: implicit data type in method to process data and union to include the data */
struct packet {
enum data_type {
TYPE_1,
TYPE_2,
/* Add more types as needed */
} data_type;
union {
struct type1 type1;
struct type2 type2;
/* Add more types as needed */
} data;
};
int process_2(const struct packet* packet) {
int res = -1;
switch (packet->data_type) {
case TYPE_1:
res = process_type1(&packet->data.type1);
break;
case TYPE_2:
res = process_type2(&packet->data.type2);
break;
default:
break;
}
return res;
}
总体而言,结构和联合对于原始字节流(例如数据协议)的可移植建模存在问题:
union
类型双关,但这里的情况并非如此。至于
(const struct type1*)data
随后取消对 type1
成员的引用,无论 MISRA 如何,这都是严格的别名违规和未定义行为。 uint8_t
是否是字符类型并不重要,因为严格别名规则的字符类型例外仅在从较大类型 到 字符类型时有效,反之则不然。 (除非你使用另一个技巧,我将在下面进一步展示。)
唯一真正可移植、定义良好、符合 MISRA 的方法是编写逐个复制数据成员的序列化/反序列化例程。无论如何,如果遇到与 CPU 字节顺序不同的网络字节顺序,您都必须这样做。这样做的缺点是您必须复制数据。
否则,这是一个可能的解决方法:
放弃
struct type1
样式,以便我们可以将 type1
更改为联合类型,而无需更改整个代码。 是否有技术原因选择一种结构编码风格而不是另一种?
建立
type1
这样的联盟:
typedef union
{
struct
{
// specific members
};
uint8_t raw_data [expected_size];
} type1;
假设
uint8_t
是一种角色类型(在实践中几乎是确定的)。确定数据包的类型后,让函数采用 uint8_t*
参数。我们现在可以将该参数转换为 type1*
,因为我们现在拥有“一个联合类型,其成员与对象的有效类型兼容”(uint8_t
)。这是严格别名规则的另一个例外。
此外,我们不再在两个不相关的结构之间输入双关语,而是在传入的原始数据和可能的结构类型之一之间输入双关语。
上面的匿名结构样式是可选的,但它允许我们输入
type.member
而不是 type.some_struct.member
。 MISRA C 不反对匿名结构/联合。
我们现在可以编写一个通用 API,例如:
typedef enum {
TYPE_1,
TYPE_2,
/* ... */
TYPE_N
} data_t;
typedef result_t process_func_t (const uint8_t* raw_data);
result_t process_type1 (const uint8_t* raw_data); // type-specific protocol parser
...
process_func_t* const process[] = // branch table
{
[TYPE1] = process_type1,
[TYPE2] = process_type2,
...
};
// ensure integrity between enum and branch table:
_Static_assert(sizeof(process)/sizeof(*process) == TYPE_N,
"process look-up table inconsistent");
用途:
data_t data_type;
...
if(data_type < TYPE_1 || data_type >= TYPE_N)
{
/* defensive programming, error handling here */
}
process[data_type](raw_data);