Reputation: 39
I have a structure used to contruct messages to a control board I need to maintain software compatibility between a C167 16-bit Keil compiler and a 32-bit Tricore gcc compiler.
typedef struct
{
unsigned char new_weld_status[2];
UINT32 new_weld_count;
UINT16 new_weld_fail_count;
} NEW_PULSE_DATA;
The array new_weld_status[2]
takes up 2 bytes on the 16-bit compiler but 4 bytes on the 32-bit compiler. I was thinking of replacing all the new_weld_status[2]
with a union when compiling with gcc. But is there a switch I can use for gcc that makes the chars fits/aligns in 2 bytes?
Thanks
Upvotes: 3
Views: 2188
Reputation: 4444
Note that your structure layout creates the problem on a 32-bit system. Many (most) 32-bit CPU architectures require 4-byte alignment for 32-bit words, thus the new_weld_count requires 'padding' to provide proper memory alignment.
typedef struct
{
unsigned char new_weld_status[2]; //a
//char padding_1[2]; //hidden padding
UINT32 new_weld_count; //a
UINT16 new_weld_fail_count; //a
} NEW_PULSE_DATA;
The following redefinition of your structure completely avoids the problem.
typedef struct
{
UINT32 new_weld_count; //a
UINT16 new_weld_fail_count; //a
unsigned char new_weld_status[2]; //a
} NEW_PULSE_DATA;
NEW_PULSE_DATA ex_PULSE_DATA;
However, the above approach is not the approach typically to transport struct(ured) data across networks/over message transports. A more common and much better approach is to use a serialization/deserialization layer (aka marshalling) to place the structures into 'over the wire' formats. Your current approach is conflating the in-memory storage and addressing with the communication format.
//you need to decide on the size of wire format data,
//Both ends of the protocol must agree on these sizes,
#define new_weld_count_SZ sizeof(ex_PULSE_DATA.new_weld_count)
#define new_weld_fail_count_SZ sizeof(ex_PULSE_DATA.new_weld_fail_count)
#define new_weld_status_SZ sizeof(ex_PULSE_DATA.new_weld_status)
//Then you define a network/message format
typedef struct
{
byte new_weld_count[new_weld_count_SZ];
byte new_weld_fail_count[new_weld_count_SZ];
byte new_weld_status[new_weld_count_SZ];
} MESSAGE_FORMAT_PULSE_DATA;
Then you would implement serialization & deserialization functions on both ends of the transport. The following example is simplistic, but conveys the gist of what you need.
byte*
PULSE_DATA_serialize( MESSAGE_FORMAT_PULSE_DATA* msg, NEW_PULSE_DATA* data )
{
memcpy(&(msg->new_weld_count), data->new_weld_count, new_weld_count_SZ);
memcpy(&(msg->new_weld_fail_count), data->new_weld_fail_count, new_weld_fail_count_SZ);
memcpy(&(msg->new_weld_status), data->new_weld_status, new_weld_status_SZ);
return msg;
}
NEW_PULSE_DATA*
PULSE_DATA_deserialize( NEW_PULSE_DATA* data, MESSAGE_FORMAT_PULSE_DATA* msg )
{
memcpy(data->new_weld_count, &(msg->new_weld_count), new_weld_count_SZ);
memcpy(data->new_weld_fail_count, &(msg->new_weld_fail_count), new_weld_fail_count_SZ);
memcpy(data->new_weld_status, &(msg->new_weld_status), new_weld_status_SZ);
return msg;
}
Note that I have omitted the obligatory network byte order conversions, because I assume your have already worked out your byte order issues between the two cpu domains. If you have not considered byte-order (big-endian vs. little-endian), then you need to address that issue as well.
When you send a message, the sender does the following,
//you need this declared & assigned somewhere
NEW_PULSE_DATA data;
//You need space for your message
MESSAGE_FORMAT_PULSE_DATA msg;
result = send(PULSE_DATA_deserialize( &data, &msg ));
When you receive a message, the recipient does the following,
//recipient needs this declared somewhere
NEW_PULSE_DATA data;
//Need buffer to store received data
MESSAGE_FORMAT_PULSE_DATA msg;
result = receive(&msg,sizeof(msg));
//appropriate receipt checking here...
PULSE_DATA_deserialize( &data, &msg );
Upvotes: 6
Reputation: 142005
Union wouldn't change the members alignment inside a struct. You are interested in padding. The compiler may insert any number of bytes/bits between struct members to satisfy alignment requiremens. On gcc compatible compilers you may use __attribute__((__packed__))
as Acorn already pointed out, but this does not take care of endianess. The most compatible version between platforms (including platforms with different alignment and different endianess) would be to use (sadly!) get/set functions that look like this:
typedef struct {
unsigned char data[2+4+2];
} NEW_PULSE_DATA;
unsigned char NEW_PULSE_DATA_get_new_weld_status(NEW_PULSE_DATA *t, size_t idx) {
return t->data[idx];
}
void NEW_PULSE_DATA_set_new_weld_status(NEW_PULSE_DATA *t, size_t idx, unsigned char value) {
t[idx] = value;
}
UINT32 NEW_PULSE_DATA_get_new_weld_count(NEW_PULSE_DATA *t) {
return (UINT32)t->data[2]<<24
| (UINT32)t->data[3]<<16
| (UINT32)t->data[4]<<8
| (UINT32)t->data[5];
}
void NEW_PULSE_DATA_set_new_weld_count(NEW_PULSE_DATA *t, UINT32 val) {
t->data[2] = val>>24;
t->data[3] = val>>16;
t->data[4] = val>>8;
t->data[5] = val;
}
UINT16 NEW_PULSE_DATA_get_new_weld_fail_count(NEW_PULSE_DATA *t) {
return (UINT16)t->data[6]<<8
| (UINT16)t->data[7];
}
void NEW_PULSE_DATA_set_new_weld_fail_count(NEW_PULSE_DATA *t, UINT16 val) {
t->data[6] = val>>8;
t->data[7] = val;
}
This is the only "good" way of being 100% sure, that NEW_PULSE_DATA looks exactly the same on different platforms (at least on platforms with the same number of bits per char/CHAR_BIT value). However sizeof(NEW_PULSE_DATA)
may be still different between platforms, because compiler may insert padding on the end of the struct (after the last member of the structure). So you may want to change NEW_PULSE_DATA
type to be just an array of bytes:
typedef unsigned char NEW_PULSE_DATA[2+4+2];
unsigned char NEW_PULSE_DATA_get_new_weld_status(NEW_PULSE_DATA t, size_t idx) {
return t[idx];
}
unsigned char NEW_PULSE_DATA_set_new_weld_status(NEW_PULSE_DATA t, size_t idx, unsigned char value) {
t[idx] = value;
}
UINT32 NEW_PULSE_DATA_get_new_weld_count(NEW_PULSE_DATA t) {
return (UINT32)t[2]<<24
| (UINT32)t[3]<<16
| (UINT32)t[4]<<8
| (UINT32)t[5];
}
void NEW_PULSE_DATA_set_new_weld_count(NEW_PULSE_DATA t, UINT32 val) {
t[2] = val>>24;
t[3] = val>>16;
t[4] = val>>8;
t[5] = val;
}
UINT16 NEW_PULSE_DATA_get_new_weld_fail_count(NEW_PULSE_DATA t) {
return (UINT16)t[6]<<8
| (UINT16)t[7];
}
void NEW_PULSE_DATA_set_new_weld_fail_count(NEW_PULSE_DATA t, UINT16 val)
{
t[6] = val>>8;
t[7] = val;
}
Upvotes: 4
Reputation: 26194
For gcc
and other compilers, you can use __attribute__((packed))
:
This attribute, attached to struct or union type definition, specifies that each member (other than zero-width bit-fields) of the structure or union is placed to minimize the memory required.
Upvotes: 1