设计处理各种数据类型的方法和数据结构

问题描述 投票:0回答:1

我需要一种 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;
}
c software-design misra
1个回答
0
投票

总体而言,结构和联合对于原始字节流(例如数据协议)的可移植建模存在问题:

  • 可能存在对齐问题以及填充问题。 MISRA C (11.3) 反对狂野和疯狂的指针转换的主要论点是对齐。
  • 可能存在不同的网络字节序。
  • 原始字节流和结构/联合之间的类型双关可能会导致“严格别名”未定义的行为。
  • MISRA C 禁止将同一内存区域用于两个不相关的目的。您只能在同一数据的不同表示之间使用
    union
    类型双关,但这里的情况并非如此。

至于

(const struct type1*)data
随后取消对
type1
成员的引用,无论 MISRA 如何,这都是严格的别名违规和未定义行为。
uint8_t
是否是字符类型并不重要,因为严格别名规则的字符类型例外仅在从较大类型 字符类型时有效,反之则不然。 (除非你使用另一个技巧,我将在下面进一步展示。)

唯一真正可移植、定义良好、符合 MISRA 的方法是编写逐个复制数据成员的序列化/反序列化例程。无论如何,如果遇到与 CPU 字节顺序不同的网络字节顺序,您都必须这样做。这样做的缺点是您必须复制数据。


否则,这是一个可能的解决方法:

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);
© www.soinside.com 2019 - 2024. All rights reserved.