8
$\begingroup$

In C a structure can have an arbitrary amount of padding. In theory this implementation conforms to the C standard:

struct X {
    int x;
    // 1000000000 bytes padding
};
// sizeof(struct X) == 1000000004

Is there any reason to permit arbitrary (seemingly meaningless) padding? (This will never be found in practice but still a possibility in theory) Is there any reason to not simply:

Structs may add up to the amount of padding required to ensure that the struct and all of the members are properly aligned.

This would put an upper bound on the size of structures, and would ensure consistent memory layouts if a programmer ensures that all members are properly aligned to begin with. I thought purpose of padding was to ensure alignment of the members. If so, why permit padding beyond the bare minimum to ensure the alignment of the members (which may be none if all the members are already aligned)?

$\endgroup$
3
  • $\begingroup$ Is that actually legal? I’m not sure about C, but at least Swift guarantees that a struct with exactly one member will have the same memory layout as its single member. $\endgroup$
    – Bbrk24
    Jun 13, 2023 at 20:10
  • $\begingroup$ @Bbrk24 I believe C makes no such guarantee. $\endgroup$ Jun 13, 2023 at 20:11
  • $\begingroup$ Since alignment is implementation defined, making padding depend on alignment would not actually change anything. I suspect it is just a matter of simplicity to just say alignment and padding are both implementation defined, without drawing any connection between them in the spec, even though the two are connected in reality. $\endgroup$
    – Chris Dodd
    Sep 14, 2023 at 5:05

9 Answers 9

7
$\begingroup$

In some applications, complete ABI stability is required - for example, the syscall interface to the Linux kernel. This means you can't change the size of a struct, because it would break old code. But sometimes the API is not finalised and you know you might want to add more fields to the struct in future - in these cases, it is common to add a large "buffer" of redundant padding to the struct so that more fields can be added later on without changing its size.

$\endgroup$
1
  • 3
    $\begingroup$ While a true statement, this seems to be totally unconnected to the question. Generally padding of this form is/must be explicitly declared in the struct or it won't necessarily exist. So this is not padding added by the language implementation. $\endgroup$
    – Chris Dodd
    Sep 14, 2023 at 5:07
10
$\begingroup$

There's two questions into one here:

Would a structure ever require padding beyond what is required to align the members?

Maybe? That is an implementation detail, and C tells you it doesn't care about it.
Also you would need to expand on what you mean by "required to align the members." Required by RAM? The I/O bus? The memory bus? Cache lines? Registers? They all can have different requirements!

Is there any reason to permit arbitrary (seemingly meaningless) padding?

Now why does C word this to leave it as an implementation detail?

C makes such liberal choices because it was written at a time when "consumer hardware" was virtually non-existant, and business machines had no international standards on their memory addressing and object sizing mechanisms.
Remember C was written for the PDP-11 which has its own endianness - objects laid out with the most significant word first, and words laid out with the least significant byte first - and whose predecessor, the PDP-8, has 12 bits addressable bytes, and 6 bits characters.
This kind of historical decisions is also why the size of C characters is implementation-defined, with the requirement that all "valid" characters be non-negative even when in signed objects, limiting them to 7 bits in machines with 8 bits addressable bytes like x86, amd64 or ARM, with the possible 1bit extension for sign left as an implementation detail.

Even in today's, "consumer hardware," some operating systems such as Windows CE have toolchains with minimal 16bits-aligned addressing.

C must be compatible with all those machines.
This is why C23§6.2.6.1.1. says The representations of all types are unspecified [...]., why §6.2.8.1. says An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated, and why §6.5.3.4.5. says The value of the result of [the sizeof and alignof] operators is implementation-defined.

C code must be able to run on a machine where RAM has 18bits bytes, characters are 6bits, the memory bus is 24bits, and registers are 32bits, and where load instructions only accept multiples of 18 up to 108.
Oh, also did you know x86 has a sub-ISA with bit addresses?

$\endgroup$
6
  • $\begingroup$ Also you would need to expand on what you mean by "required to align the members." Required by RAM? The I/O bus? The memory bus? Cache lines? Registers? They all can have different requirements! How can these be different? As far as I knew 'aligned' just means 'memory address of object is a multiple of the size of the object.' $\endgroup$ Jul 6, 2023 at 19:40
  • $\begingroup$ "Memory address" but those are all different kinds of memory! Say you have a 1byte object in a RAM with 12bits byte boundaries, and try to copy it to a 24bits bus with a CPU whose ISA only allow you a 24bits move. Those two pieces of hardware have different alignment requirements. Are you just gonna waste a zeroed-out byte in RAM to make the bus happy? What if then the programme copies that object from the bus to a 36bits register? $\endgroup$
    – Longinus
    Jul 6, 2023 at 22:42
  • $\begingroup$ @user16217248-OnStrike And then if you figure out a general solution with powers-of-two padding, try to port it to 31bits CPUs, on which some Linux systems do run. $\endgroup$
    – Longinus
    Jul 6, 2023 at 22:53
  • $\begingroup$ Does the PDP-11 hardware lay things out that way, or was that merely a choice of whoever first added the "long" type to PDP-11 compilers? $\endgroup$
    – supercat
    Sep 12, 2023 at 14:54
  • $\begingroup$ Just as a note, C23 supports "interchange types" which have a known bit layout, e.g. IEEE-754 floating point. $\endgroup$
    – Pseudonym
    Sep 15, 2023 at 10:15
7
$\begingroup$

One possible use case for more complex padding is for something like OpenCL, targeting GPUs with peculiar local memory layouts, where you may want to ensure that threads (warps, whatever else they're called by different vendors) access their local memory banks only to avoid stalling each other, meaning the structures need to have large padding in between elements to fit into a layout like Bank1 Bank2 Bank3 Bank4 Bank1 Bank2 ...

$\endgroup$
5
$\begingroup$

A few ideas:

  • Some implementations might wish to have all struct fields in the order they're declared, e.g. for binary compatibility with other programs, file formats or network protocols. This might mean more padding than the minimum required amount, so a rule requiring minimal padding would forbid such implementations.
  • Memory allocation can be easier if it's always done in powers of two; alternatively, if non-powers-of-two are allowed, it might still help to pad one struct out so that it has the same size as another struct, minimising the number of different sizes of allocation.
  • Padding a smaller field to occupy a whole machine word may allow more efficient accesses, particularly if atomic accesses are required and the target architecture only supports atomic accesses of whole words.
$\endgroup$
4
  • $\begingroup$ If you need compatibility with some specification, you usually do that by declaring explicit members for those extra spaces. $\endgroup$
    – Barmar
    Sep 8, 2023 at 21:24
  • $\begingroup$ "the target architecture only supports atomic accesses of whole words." Isn't that padding implied by "requires to ensure that all of the members are properly aligned"? $\endgroup$
    – Barmar
    Sep 8, 2023 at 21:25
  • $\begingroup$ @Barmar No ─ for example, a value sized 1 byte would be aligned at any address, whereas a 4-byte-sized value would be aligned at addresses which are a multiple of 4. Otherwise you would not be able to store an array compactly and take a pointer to one of its elements. So for example a struct of two 32-bit ints on a 64-bit machine would only require 8 bytes to properly align both fields, but would require 16 bytes if either or both fields require atomic accesses which can't be done at the sub-word level on a given machine. $\endgroup$
    – kaya3
    Sep 9, 2023 at 9:47
  • $\begingroup$ "Some implementations might wish to have all struct fields in the order they're declared": The C standard effectively requires this; 6.7.2.1p15 says the addresses of struct members must increase in the order in which they are declared. $\endgroup$ Sep 14, 2023 at 4:34
3
$\begingroup$

Consider:

typedef struct {
    double d;
    char c;
} S;
static S a[2];
  • The address of a[1] must be on a double boundary.
  • &a[1] is &a + sizeof(S).
  • Therefore sizeof(a[0]) must be a multiple of a double boundary.
  • Therefore the structure requires padding at the end beyond what is required to align the members.
$\endgroup$
3
  • 1
    $\begingroup$ Swift handles this by differentiating size and stride. The size of S is 9, but the stride of S is 16. Arrays use the stride, but other things (such as wrapping it in a larger struct) use the size. $\endgroup$
    – Bbrk24
    Jun 15, 2023 at 12:59
  • 1
    $\begingroup$ @Bbrk24, that sounds like the right way to handle it. (Though obviously people need to be very aware of this distinction, as the common auto S *s = malloc(n * sizeof(s)) could be wrong.) $\endgroup$ Jun 15, 2023 at 13:18
  • $\begingroup$ Yeah, Swift provides its own allocate(capacity:) function so that you don't have to do that calculation yourself most of the time. $\endgroup$
    – Bbrk24
    Jun 15, 2023 at 13:21
3
$\begingroup$

On some platforms, there may be benefits to padding array elements to a power-of-two size, and on some others (though probably mostly obsolete ones by now) there may be advantages to padding arrays that would otherwise have a power-of-two size. For example, given static-duration struct row { char dat[8]; } arr[20]; if i and j are in registers, incrementing arr[i].dat[j] would be a single instruction on x86, but if struct row were 7 bytes rather than 8, it would be necessary to add another instruction to perform effective address computation.

$\endgroup$
3
$\begingroup$

One reason might be performance. A concrete example would be avoiding false cache sharing on shared-memory multiprocessors. Basically, sometimes you'll want to pad a struct so that it occupies a whole cache line completely, so that no other data could be fitted on there as to prevent two processors sharing the same cache line, albeit never accessing the same data.

$\endgroup$
2
$\begingroup$

One possible use is to prevent object slicing. For example, it could allow storing a subtype in a variable declared to be in the base type. In C++, it would allow returning a subtype in the implementation of a virtual function, and make it possible to have a real enumerable interface like other languages, without changing the iterator to a reference type.

This may create problems and unnecessary noises when the padding is not enough for another programmer trying to extending the class not knowing the implementation details. But one possible solution is to add a pointer and put the extra fields somewhere else in that case. It's like falling back to a reference type when a value type is not possible.

$\endgroup$
2
  • $\begingroup$ The question is about structs, not classes ─ does C or C++ support subtype relationships between structs? $\endgroup$
    – kaya3
    Sep 9, 2023 at 12:27
  • $\begingroup$ @kaya3 In that case, you could simulate inheritance using syntactically unrelated structs, and simulate virtual functions using function pointers. The problem is from C allocating variables on the stack assuming it having a fixed size, instead of characteristics of classes. A language could use reference types everywhere, and wouldn't have this problem, regardless of whether it support classes. $\endgroup$
    – user23013
    Sep 9, 2023 at 12:46
1
$\begingroup$

I see this issue as more philosophical than technical, and would flip the question around: if, for whatever reason, an implementation should wish to do this, why should the C standard interfere?

The C language standard is essentially a contract between the programmer and the implementation. It requires the implementation to provide certain guarantees as to its behavior, so that the programmer can write a program with confidence that its execution will be correct.

But the standard is not concerned with whether that execution will be performant in terms of time, memory usage, etc.; that's considered a "quality of implementation" issue. In fact, I can't think of any part of the C standard that lays requirements on the performance or efficiency of an implementation, except possibly the very weak environmental limits of 5.2.4.

Sometimes the standard sets rules that make it possible for implementations to do something in a particular efficient fashion. For instance, the strict aliasing and data race rules make it legal for an implementation to perform certain optimizations. But the standard never requires them to do so.

Generally, the programmer doesn't need guarantees about the layout of structs in order to write a correct program. The language has mechanisms for them to inquire about the layout of structs (e.g. offsetof) and that should be all you need. As such, based on its philosophy, the standard has no business imposing any mandates on struct layout, even if they seem reasonable and not onerous for existing implementations.

$\endgroup$
1
  • $\begingroup$ The environmental limits don't impose any meaningful limits on efficiency, since an implementation that can behave as though it correctly processes one particular contrived and useless program that nominally exercises the translation limits may behave in essentially arbitrary fashion when given almost any other program. $\endgroup$
    – supercat
    Sep 14, 2023 at 21:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .