强制两个结构在编译时具有相同的大小?

时间:2018-10-02 12:41:57

标签: c embedded

我定义了两个数据结构,它们必须彼此保持相同大小,以使应用程序正常运行。该结构用于在PC和DSP之间进行通信。 DSP代码在“ C”中,PC端在C ++中。

例如:

struct inbound_data{
    int header[5];
    float val1;
    float val2;
    int trailer[3];
};

struct outbound_data{
    int header[5];
    int reply1;
    int reply2;
    float dat1;
    float dat2;
    int filler[1];
}

稍后我会做类似的事情:

int tx_block[sizeof(outbound_data)];
int rx_block[sizeof(inbound_data)];

这些阵列将传递到通信外围设备,以在设备之间进行发送和接收。

由于硬件的工作原理,两个结构的大小必须匹配,以便缓冲区的大小相等是至关重要的。这很容易保证适当的注意,但是偶尔在设计周期中,数据结构会被修改。如果不是非常谨慎,并且意识到结构必须保持相同大小(并且也反映在PC端代码中)的要求,那么就会出现混乱。

我想找到一种编译时的方法,如果其中一个结构被修改,使其与另一个结构的大小不匹配,则不构建代码。

在“标准” C中是否可以通过某种方式在编译时检查大小,如果大小不同则失败? (我认为我的编译器至少是C99,也许不是11)。

5 个答案:

答案 0 :(得分:7)

如果必须使用C99,那么我也like Swordfish建议使用宏。制作可以出现在任何地方并且不会引入任何对象以使优化器删除的方法是将无效数组放入typedef中。因此,更通用的静态断言应如下所示:

#define CONCAT_(A,B) A##B
#define CONCAT(A,B) CONCAT_(A,B)
#define MY_STATIC_ASSERT(p, msg) typedef char CONCAT(dummy__,__LINE__) [(p) ? 1 : -1]

它旨在模仿_Static_assert。该消息被传递,希望编译器诊断能够显示该消息。其用法的一个示例是here

哪个会产生:

main.cpp:4:54: error: size of array 'dummy__13' is negative
 #define MY_STATIC_ASSERT(p, msg) typedef char CONCAT(dummy__,__LINE__) [(p) ? 1 : -1]
                                                      ^~~~~~~
main.cpp:2:22: note: in definition of macro 'CONCAT_'
 #define CONCAT_(A,B) A##B
                      ^
main.cpp:4:47: note: in expansion of macro 'CONCAT'
 #define MY_STATIC_ASSERT(p, msg) typedef char CONCAT(dummy__,__LINE__) [(p) ? 1 : -1]
                                               ^~~~~~
main.cpp:13:1: note: in expansion of macro 'MY_STATIC_ASSERT'
 MY_STATIC_ASSERT(sizeof(struct foo) == sizeof(struct baz), "Do not match!");

一直到那里,您都可以看到带有消息的静态断言。


事后,您可以将dummy__更改为please_check_line_,将产生上面更具描述性的please_check_line_13

答案 1 :(得分:6)

C11标准添加了一个新关键字_Static_assert。您可以使用它在编译时测试谓词,如果它是false,则会产生错误:

_Static_assert(sizeof(outbound_data) == sizeof(inbound_data), "sizes must match");

答案 2 :(得分:6)

  

在编译时强制两个结构具有相同的大小吗?

在C中没有强制执行此操作的标准方法。只有防止它发生的方法,例如static_assert-可以防止错误代码编译但无法解决实际的问题。

在您的情况下,有几个问题:

  • 您的结构体使用的是C的天真默认类型。这些类型不可移植,可以具有任意大小。通过将int换成int32_t等,可以很容易地解决此问题。
  • Endianess可能会使代码不可移植,而与整数类型无关。这是一个单独的问题,我不会在这里解决,但是需要考虑,特别是对于特殊的DSP。
  • 任何结构都可以在任何地方包含填充字节,以满足系统特定的对齐要求。问题的根源是对齐在不同系统上的工作方式不同。这是很难解决的。

避免填充的肮脏修补程序是将static_assert与一些非标准解决方案一起使用,以确保结构具有预期的大小。例如#pragma pack(1)或gcc __attribute__ ((__packed__))等。这些不是标准的,也不是可移植的。此外,在许多系统上,跳过填充可能会有问题,并且访问不对齐可能会遇到问题-填充是有原因的。因此,这可能会带来更多无法解决的问题。

因此,不幸的是,我们最终意识到struct不适合可移植代码。特别是对于诸如数据协议规范之类的东西。

如果您需要真正可移植的,坚固的代码,则只剩下一个选择,即使用uint8_t的原始数据数组。如果需要将此数组转换为结构,则必须编写序列化/反序列化代码。这将花费运行时开销。但是,如果您要真正的可移植结构,那就别无选择了。

答案 3 :(得分:3)

对于C99,您可以使用类似

#define C_ASSERT(x, y) { int dummy[(x) == (y) ? 1 : -1]; (void*)dummy; }

struct foo {
    int f;
};

struct bar {
    int b1;
    //int b2;
};

int main()
{
    C_ASSERT(sizeof(struct foo), sizeof(struct bar));
}

答案 4 :(得分:1)

您可以添加填充以使尺寸相等

struct inbound_data;
struct outbound_data;

struct _inbound_data{
    int header[5];
    float val1;
    float val2;
    int trailer[3];
};

struct _outbound_data{
    int header[5];
    int reply1;
    int reply2;
    float dat1;
    float dat2;
    int filler[1];
};

struct inbound_data{
    int header[5];
    float val1;
    float val2;
    int trailer[3];
    char padding[sizeof(struct _inbound_data) < sizeof(struct _outbound_data) ? sizeof(struct _outbound_data) - sizeof(struct _inbound_data) : 0];
};

struct outbound_data{
    int header[5];
    int reply1;
    int reply2;
    float dat1;
    float dat2;
    int filler[1];
    char padding[sizeof(struct _outbound_data) < sizeof(struct _inbound_data) ? sizeof(struct _inbound_data) - sizeof(struct _outbound_data) : 0];
};

我当然可以在不重复struct成员的情况下用更短的方式编写-但是我故意这样做是为了展示这个想法。

struct inbound_data1 __attribute__((packed){
    struct _inbound_data id;
    char padding[sizeof(struct _inbound_data) < sizeof(struct _outbound_data) ? sizeof(struct _outbound_data) - sizeof(struct _inbound_data) : 0];
};

struct outbound_data1 __attribute__((packed){
    struct _outbound_data od;
    char padding[sizeof(struct _outbound_data) < sizeof(struct _inbound_data) ? sizeof(struct _inbound_data) - sizeof(struct _outbound_data) : 0];
};