我需要一种C语言中的方法,能够处理各种格式/类型的数据包.您推荐的方法是什么(请注意,我希望尽可能符合MISRA).目前,我正在考虑以下选项(但愿意学习其他选项),根据下面的代码oullize

选项1:方法显式声明数据包的格式(并且方法内部有切换用例) 选项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;
}

推荐答案

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

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

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

唯一真正可移植、定义良好、符合MISRA的方法是编写序列化/反序列化 routine ,逐个成员复制数据成员.无论如何,您都必须这样做,以防面临与您的中央处理器顺序不同的网络顺序.缺点是您必须复制数据.


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

typedef union
{
  struct
  {
    // specific members
  };
  uint8_t raw_data [expected_size];
} type1;

这假设uint8_t是一种字符类型(在实践中几乎完全确定).确定数据包类型后,让函数采用uint8_t*参数.我们现在可以将该参数转换为type1*,因为我们现在拥有"一个具有与对象有效类型兼容的成员的联合类型"(uint8_t).这是严格别名规则的另一个例外.

此外,我们不再在两个不相关的 struct 体之间输入双关语,而是在输入的原始数据和可能的 struct 体类型之一之间输入双关语.

上面的匿名 struct 样式是可选的,但它允许我们输入type.member而不是type.some_struct.member.MISRA C不反对匿名 struct /联合.

我们现在可以制作一个通用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 */
}

// assuming raw_data is aligned as per the requirements of the various struct types, then:
process[data_type](raw_data);

这是安全相关软件的非常标准的代码.据我所知,除了我们在process_type1内放置的演员阵容从uint8_t*type1*等之外,符合MISRA.MISRA可能有必要偏离规则11.3.我们不希望永久性地偏离这一规则,因为这是一个非常合理的规则.

C++相关问答推荐

char为16位且Short也为16位的c环境合法吗

在struct中调用函数,但struct在void中 *

MISRA C:2012 11.3违规强制转换(FLOAT*)到(uint32_t*)

我编译了一个新的c程序,并收到以下错误

我无法让LLDB正确运行我的可执行文件

_泛型控制表达式涉及数组碰撞警告的L值转换错误?

文件权限为0666,但即使以超级用户身份也无法打开

Clang警告称,`ffi_type`与`sizeof`不兼容

CC2538裸机项目编译但不起作用

在for循环中指向数组开头之前

存储和访问指向 struct 的指针数组

C: NULL>;NULL总是false?

函数指针作为函数参数 - 应该使用 const 吗?

为什么孤儿进程在 Linux 中没有被 PID 1 采用,就像我读过的一本书中声称的那样?

将数组中的所有元素初始化为 struct 中的相同值

System V 消息队列由于某种原因定期重置

传递参数:C 和 C++ 中 array 与 *&array 和 &array[0] 的区别

如何用用户输入的多个字符串填充数组?

使用fread()函数读取txt文件

创建 makefile 来编译位于不同目录中的多个源文件