Reputation: 882
I have defined two data structures that must remain the same size as each other for the application to function properly. The struct's are used to communicate between a PC and a DSP. The DSP code is in 'C', the PC side in C++.
for example:
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];
}
later I will do something like:
int tx_block[sizeof(outbound_data)];
int rx_block[sizeof(inbound_data)];
These arrays will be passed to the communication peripherals to transmit and receive between the devices.
Because of how the hardware works, it is essential that the size of the two structs match, so that the buffers are of equal size. This is easy enough to assure with proper care, but occasionally through the design cycle, the data structures get modified. If one is not extremely careful, and aware of the requirement that the structures stay the same size (and be reflected in the PC side code as well), chaos ensues.
I would like to find a compile time way to have the code not build if one of the structures gets modified so that it does not match the size of the other structure.
Is this possible somehow in 'standard' C to check the sizes at compile time and fail if they are different? (I think my compiler is at least C99, maybe not 11).
Upvotes: 13
Views: 1829
Reputation: 213892
Enforce two structs have same size at compile time?
There is no standard way to enforce this in C. There are only ways to protect it from happening, such as static_assert
- which prevents buggy code from compiling but doesn't solve the actual problem.
In your case there are several problems:
int
for int32_t
etc.The dirty fix to avoid padding is to use static_assert
together with some non-standard solution to ensure that the struct has the expected size. Such as #pragma pack(1)
or gcc __attribute__ ((__packed__))
etc. These are not standard nor are they portable. Furthermore, skipping padding can be problematic on many systems and you can get issues with misaligned access - padding is there for a reason. So this can potentially create more problems than it solves.
So unfortunately we end up with the realisation that struct
is unsuitable for portable code. Particularly for things like data protocol specifications.
If you need truly portable, rugged code, it leaves you with only one option, namely to use a raw data array of uint8_t
. In case you need to translate this array into structs, you will have to write serialization/de-serialization code. Which will cost run-time overhead. But there's no other way around it, if you want truly portable structs.
Upvotes: 6
Reputation: 170084
If you must use C99, then I too like Swordfish would suggest a macro. The way to make one which can appear anywhere, and would not introduce any objects for the optimizer to remove, is to put the invalid array in a typedef
. So a more general purpose static assertion would look this:
#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]
It's designed to mimic _Static_assert
. The message is passed in with the hopes of a compiler diagnostic showing it. An example for its usage is here.
Which produces:
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!");
And all the way down there you can see the static assertion with the message.
As an afterthought, you can change dummy__
to please_check_line_
will will produce the more descriptive please_check_line_13
above.
Upvotes: 8
Reputation: 67546
you can add padding to equalize the size
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];
};
I it can be of course written shorter way without the struct members duplication - but I did it intentionally to show the idea.
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];
};
Upvotes: 1
Reputation: 13134
For C99 you could use something like
#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));
}
Upvotes: 3
Reputation: 93294
The C11 Standard added a new keyword _Static_assert
. You can use it to test a predicate at compile-time, and produce an error if it is false
:
_Static_assert(sizeof(outbound_data) == sizeof(inbound_data), "sizes must match");
Upvotes: 7