I know that structs in C may not be laid out memory as they are in the code. For example:
struct a {
short x;
int y;
};
assuming 2 byte shorts and 4 byte ints, may actually take 8 bytes in memory as the compiler wants to align the members on 4 byte boundaries ... so there is 2 bytes of slack between x and y.
This makes reading and writing structs unportable across language, compiler, and hardware. The only way to read and write them is member by member. Yes, Endianness is also an issue here and swapping must be done at the member level but lets assume this is not an issue.
Fortran has a 'sequence' specifier for derived types (structures) that tells the compiler to lay out the members in memory as they are given. This allows portable reading and writing of derived types.
My question is: Is there any way to do a similar thing in C in a portable (and maintainable) way?
Yes, Fortran 2003 introduced the bind(C)
specifier which tells the compiler to do exactly the same as the companion C compiler does
type, bind(C) :: a
components
end type
Additionally, the iso_c_binding
module (a subpart of the whole Fortran 2003 C interoperability) defines constants which help you connect the C and Fortran intrinsic types:
use intrinsic :: iso_c_binding, only: c_short, c_int
type, bind(C) :: a
integer(c_short) :: x
integer(c_int) :: y
end type
Such type is said to be interoperable with your C struct.
The C interoperability is very widely supported in compilers. It is very difficult to find a compiler which does not support this feature and is still supported by its vendor.
Stay away from sequence
when mixing C and Fortran. Sequence types cannot be made interoperable according to the standard.
There is no 100% clean solution that I know of. What I typically do is create two header files called pack_on.h
and pack_off.h
. To define a structure without padding:
#include <pack_on.h>
struct a {
short x;
int y;
} PACKED_STRUCTURE;
#include <pack_off.h>
The header files contain the pragmas or whatever is needed to satisfy your compiler, i.e. something like:
#ifdef _MSC_VER
#pragma pack(1)
#define PACKED_STRUCTURE /* nothing */
#endif
#ifdef __GNUC__
#define PACKED_STRUCTURE __attribute__(packed)
#endif
As you already said, when binary layout is an issue, endianness is typically also important. This is not addressed in the above solution.
Please note that it is also a bad idea to use packed structures throughout your entire code. The padding which is added by the compiler when you don't force these packed structures is useful for fast memory access.
In many applications it is best to use these packed structures only in one layer of your application which deals with reading and writing these packed structures, and copy them into properly aligned structures before passing them on into the core of your application for further processing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With