For example, say I have two equivalent structs a and b in different projects:
typedef struct _a
{
int a;
double b;
char
Yes. You should always assume deterministic behaviour from your compiler.
[EDIT] From the comments below, it is obvious there are many Java programmers reading the question above. Let's be clear: C structs do not generate any name, hash, or the likes in object files, libraries, or dlls. The C function signatures do not refer to them either. Which means, the member names can be changed at whim - really! - provided the type and order of the member variables is the same. In C, the two structures in the example are equivalent, since packing does not change. which means that the following abuse is perfectly valid in C, and there's certainly much worse abuse to be found in some of the most widely-used libraries.
[EDIT2] No one should ever dare to do any of the following in C++
/* the 3 structures below are 100% binary compatible */
typedef struct _a { int a; double b; char c; }
typedef struct _b { int d; double e; char f; }
typedef struct SOME_STRUCT { int my_i; double my_f; char my_c[1]; }
struct _a a = { 1, 2.5, 'z' };
struct _b b;
/* the following is valid, copy b -> a */
*(SOME_STRUCT*)&a = *(SOME_STRUCT*)b;
assert((SOME_STRUCT*)&a)->my_c[0] == b.f);
assert(a.c == b.f);
/* more generally these identities are always true. */
assert(sizeof(a) == sizeof(b));
assert(memcmp(&a, &b, sizeof(a)) == 0);
assert(pure_function_requiring_a(&a) == pure_function_requiring_a((_a*)&b));
assert(pure_function_requiring_b((b*)&a) == pure_function_requiring_b(&b));
function_requiring_a_SOME_STRUCT_pointer(&a); /* may generate a warning, but not all compiler will */
/* etc... the name space abuse is limited to the programmer's imagination */