struct alignment question

Started by
8 comments, last by Bregma 10 years, 11 months ago
Hallo.

I understand that the size of a struct is not always the sum of it's innards, because there can be padding inbetween fields. What I am wondering is this:

Can I safely assume that two differently named structs types are the same (have the same size and same field offset) if they have the same arrangement of fields?

That is, can I assume that these two are the same:

struct a{
    int a;
    char b;
    long c;
    short d;
    double e;
};

struct a{
    int a;
    char b;
    long c;
    short d;
    double e;
};
even when they are defined in completely different places (different files, or different scope, or maybe anonymously defined)?
Advertisement

Yes and no ;)

The names of the fields doesn't matter, just the type and ordering, so yes.

But you can change the packing with #pragma pack (or with a compile time switch) (on Visual C++ anyway, other compilers may have different options/pragmas), so no. The alignment can be different when the source or header file is compiled depending on what packing is active when the struct/class definition is parsed.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

Can I safely assume that two differently named structs types are the same (have the same size and same field offset) if they have the same arrangement of fields? That is, can I assume that these two are the same, even when they are defined in completely different places (different files, or different scope, or maybe anonymously defined)?

Yes, their layout in memory should be the same.

For example, if you include the same structure declaration in various source files the objects must be interchangeable because they are the same layout. If you have the object in foo.c and also in bar.c, you can pass the objects between functions in either file without problem.

As Paradigm Shifter pointed out, you can mess this up on your own by changing the alignment or padding options for the compiler, but that breaks lots of things. Don't do that.

The bigger question is WHY you would have 2 identical structs defined in multiple places which you wanted to treat as the same in multiple places. That's what header files are for.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley
Ah, so as long as I don't use pragmas or __attribute__, it should be the same.

The reason I'm doing this is for convenience (and I'd say clarity of the code too).

I have this hash set type, and it can be used as a hash table if I just pass in a pair of values as a key. The thing is, I need to define a new struct for every pair (no std::pair<a, b> template in c). Rather than defining them at the top of the file with names that I might not remember, I think it is better to define it in the function where I actually need to use it.

What does your implementation for your hash map look like? Does it use void* and size parameters or is it built with #define macros?

You might be better off defining an interface of function pointers instead and use that in the same way you would an abstract class in C++, (i.e. a list of function pointers like a vtbl) and passing one of those in to the create function of your hash map (which would be stored in the map and would call out to functions provided by concrete implementations for each type, like how qsort does for the comparison operator). Then your concrete implementations can cast from void* to the required type and back.

Or you could just use C++ ;)

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley
It looks like this:
When I create a new hash set, I pass in the element size, a comparison function pointer, and a hash function pointer. Adding a key involves using memcpy to copy "element size" number of bytes into the hash set's array. So yes, it's void* and element size.

Basically, I un-macro-ized the original macro implementation because it was getting messy to put things like "MAKE_HASHSET(int, INT_CMP, INTHASH)" everywhere.

The vtable you described there, what function pointer is needed? (besides comparison and hash)

That should do it (comparison and hash).

You can also store the element size and keysize when you create the hash map, then you can express the entire interface using void*, but you do lose the ability to use a literal key then (need to use a variable with an address).

hashmap* pMap = CreateHashSet(sizeof(int) /*keysize*/, sizeof(int) /* element size */, INT_CMP /* comparison func */. INTHASH /* hash func */);

int myKey = 0;

int myValue = 42;

pMap->InsertElement(&myKey, &myValue);

where the insert uses sizeof(int) stored as the keysize to copy the element, and InsertElement just uses void* parameters (size is known by the data associated with the map).

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley
That's pretty much how it is. Except it only takes keys as an argument.

/* inside a function*/
typedef struct int_pair{
    int key, val;
}int_pair;
struct hashset * hs = hashset_new(sizeof(int_pair), &int_cmp, &int_hash);
hashset_insert(hs, &(int_pair){0, 42});
It's true that it can't take literals, but it still behaves as if it is because it does make a copy of the thing.

The bigger question is WHY you would have 2 identical structs defined in multiple places which you wanted to treat as the same in multiple places. That's what header files are for.

In fact, that's what header files DO.

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement