2

I'm currently defining a struct in C (in Ubuntu x64). It looks like this:

#include <semaphore.h>
#include <stdio.h>
#include <stdbool.h>

typedef struct key{
    sem_t sem;
    char name[32];
    int val1;
    int val2;
    char k;
    int n;
} Key;

From what I know (correct me if wrong, please), struct members in x64 will align to 8 bytes and in x32 will align with 4 bytes. That's also the reason I chose 32 as the id array size.

What I wanted to know is: as it is (first member being a sem_t (32 bytes apparently), and next members being that or any other thing) will there be any padding between the first (sem) and the second member (name, in this case)? Or are they contiguous? If so, does this hold true for both x32 and x64 (as 32 bytes is a multiple of both 4 and 8 bytes)?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Raul Sofia
  • 33
  • 3
  • 3
    Struct members are not all aligned to 8 just because you're compiling for x64, it should depend on their natural alignment (unless overridden). See eg [Structure padding and packing](https://stackoverflow.com/q/4306186/555045) – harold May 14 '23 at 03:26
  • @JonathanLeffler: Oh I see. So maybe using a character string as the second member wasn't a good example. what if the second member was an int, like for example if val1 was directly after sem? would this depend on the size of sem_t? If sem_t has not 32 bytes but is still a multiple of 4 and 8 would there be any padding? – Raul Sofia May 14 '23 at 03:31
  • If `sizeof(sem_t) == 28`, for example, and you had an `int` as the next member of the structure, there wouldn't be any padding bytes. OTOH, if you had a `double` as the next member and your system required `double` to be aligned on an 8-byte boundary (x86/64 but x86/32 seems to allow 4-byte alignment for `double`), then you'd have four padding bytes between `sem` and the next element. – Jonathan Leffler May 14 '23 at 03:47
  • I'd expect no padding except after `char k`; alignof(T) is at most 4 for the int members, assuming `sizeof(int) == 4` like all modern mainstream 32 and 64-bit C implementations. Rules for struct layout depend on the ABI; see also [How do I organize members in a struct to waste the least space on alignment?](https://stackoverflow.com/q/56761591) and also [Why is the "alignment" the same on 32-bit and 64-bit systems?](https://stackoverflow.com/q/55920103) re: the struct-layout rules in Windows x64, which are non-obvious. – Peter Cordes May 14 '23 at 09:21
  • You say "x32", but that doesn't mean what you probably think it does (32-bit x86). It means 32-bit pointers *in 64-bit mode* on x86-64. https://en.wikipedia.org/wiki/X32_ABI. See also [The most correct way to refer to 32-bit and 64-bit versions of programs for x86-related CPUs?](https://stackoverflow.com/q/53364320) for accurate terminology. Also note that x86 isn't the only architecture around; AArch64, 32-bit ARM, and RISC-V 32 and 64 are also relevant for mainstream usage. – Peter Cordes May 14 '23 at 09:23

1 Answers1

2

It depends — on the implementation. One question you need to be sure you know the answer to: Why are you worried?

The size of a sem_t may vary between 32-bit and 64-bit implementations. In general, when N is a power of two, an N-byte object (or an array of N-byte objects) will be aligned on an N-byte boundary. The alignment for double doesn't always follow this rule. If you had any short (or uint16_t) data members, they would only need to be aligned on a 2-byte boundary*. A structure or union type will be aligned according to the alignment requirements of the member with the most stringent alignment requirements.

There won't be any padding bytes between sem and name because character strings can be aligned on any boundary. There might be padding bytes between name and val1, but you've probably avoided that (in part because you chose the length 32 for name).

You will have padding between k and n; if you reverse their order, you'll (probably) only have trailing padding, but you will have trailing padding because of the single character. As written, you have interior padding (probably 3 bytes of it) and maybe no trailing padding.

To investigate further, you'd need to use the offsetof macro from <stddef.h>, but be aware that it can only report on the current implementation, not other implementations.

* Technically, a short need not be 16 bits or 2 bytes, but it almost invariably is, and I'm assuming as much here.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • My only worry with that was that I was using this struct inside a shared memory. To write there, I used memcpy(), but I didn't want to overwrite the semaphore, so I used as a starting pointer keyp + sizeof(sem_t) (and size = sizeof(Key) - sizeof(sem_t)). That's why I started thinking about padding. If there was any between the first and second members, then I would be memcpying to the wrong place. – Raul Sofia May 14 '23 at 03:38
  • 1
    As long as `keyp` is a `char *`, your formula would have worked correctly, with or without padding. If you have `Key *keyp;`, then you'd have to use `(char *)keyp + sizeof(keyp->sem)` (or `sizeof(sem_t)`) to have the right starting point. Beware of GCC allowing pointer arithmetic on `void *` as if it was equivalent to `char *` — the C standard does not allow that. – Jonathan Leffler May 14 '23 at 03:43
  • 1
    @RaulSofia: It works because `alignof(char) == 1`, but if `name` were of some other type needing greater alignment, then your method could be wrong. Simpler to use `&(((Key *)keyp)->name)`, or `keyp + offsetof(Key, name)` if `keyp` is a `char *` or similar. – Nate Eldredge May 14 '23 at 03:45
  • Although I said that your calculation would work, it would (in general) only work for copying the second member onwards (it wouldn't work for copying only the second member, even — not in the general case). For other members, you'd have to be more careful, using `offsetof` and `sizeof` carefully, because there could be padding bytes to foul up any calculation based on the 'sum of the sizes'. – Jonathan Leffler May 14 '23 at 03:51