Deep C Dives: Value Structs
Written by Mike James   
Wednesday, 27 August 2025
Article Index
Deep C Dives: Value Structs
Value Semantics
Padding
Final Thoughts

Padding

The only tricky part is that a struct may actually use a larger block of memory than you might expect. The reason is “padding” due to the need for memory accesses to be aligned to word boundaries. For example in a 32-bit machine the address of a 32-bit int would have to be a multiple of four because the memory is divided into 4-byte words, accessible in a single read/write.

Some machines demand that memory accesses are aligned, others allow unaligned access, but warn that it is slower. If alignment is optional, some compilers have a facility that lets you decide if you want to pack the struct for minimum space or include padding to ensure that fields are aligned correctly for speed. What all this means is that you cannot simply assume that a struct takes the amount of space suggested by how much space its fields take.

For a 64-bit machine using a 64-bit word, a struct will take at least eight bytes.

For example:

struct test {
    int i;
    char c;
}test;
printf("%d \n", sizeof (int));
printf("%d \n", sizeof (char));
printf("%d \n", sizeof (struct test)); 

will print 4, 1 and 8 on a 64-bit Intel/Arm processor based machine. So although the struct should have taken just five bytes it actually takes eight to pad it out to a full 64-bit word.

Notice that the padding is added at the end or in between fields. The C standard ensures that no padding is added to the start of a struct and this means you can rely on the fact that the address of the struct is the address of the first field but, of course this first address will be on a word boundary.

If you want to pack a struct to take the smallest amount of memory then a rule of thumb is to allocate fields in the order of size starting with the largest.

The fact that structs are packed makes many things that you might think easy more difficult. For example, you cannot rely on pointer arithmetic to pick out fields because apart from the first field padding may move the start of a field from where you expect it to be.

Type Punning

Apart from the issue of padding, you can treat a struct as just a block of memory in the sense that you can cast it into other forms. This is an example of “type punning” where the bytes of one type are interpreted as the bytes of another. This is a very standard operation when C is used in a low-level system role and yet the whole subject is fraught with confusion. Some programmers are so convinced that type punning is a bad idea that they state that it should never be used. The fact of the matter is that it is used a lot and if it really was to be removed, a lot of code would have to be rewritten and some would be next to impossible to implement in an efficient way.

The simplest example is where a function accepts a struct as a parameter, but the struct has been extended over the years of development. Suppose it started out as:

struct myStruct{
  int a;
  int b;
};

and the function is:

void myFunction(struct myStruct *myPointer){
	printf("%d\n", myPointer->a);
	printf("%d\n", myPointer->b);
}

Now suppose that later on the struct is extended to include a new field essential to the further development of the project:

struct myStructEx{
   int a;
   int b;
   int c;
};

The good news is that new functions can make use of the new struct and the old functions can carry on treating the new struct like the old using a cast:

myFunction((struct myStruct *) myPointer);

where myPointer is now a pointer to myStructEx. Obviously myFunction knows nothing about the extended structure and so will only use the fields a and b. This is the struct analog of subclassing – myStructEx derives from myStruct in exactly the same way as a subclass derives from a base class. Cast to the “base” struct is an example of type punning where a block of memory is treated as different types.

You can take the “inheritance” idea a step further by including the base struct in the derived struct:

struct myStruct
{
	int a;
int b;
};
struct myStructEx
{
struct myStruct base;
int c;
};

Now any new functions passed myStructEx have to refer to the base struct as

myPointer->base.a;

but if you cast myStructEx to myStruct the original still works:

(myStruct *)myPointer->a

If you think that this sort of thing is rare and not something you are likely to use or encounter, it is worth pointing out that CPython is based on this way of implementing classes and objects. However, it is important to know that nearly all type punning breaks the strict aliasing rule. This basically states that a block of memory should only be referenced by pointers to compatible types, see Dive 16. In other words, one of the most used of the advanced features of C is usually illegal according to the recent C standards. This is, of course, nonsense. Use type punning wherever it makes sense and if necessary configure the compiler to swallow it without demur, the alternatives are much worse.

Notice that you do have to be careful about padding being different if you change the type of a struct. For example, if you have a struct like:

struct test1 {
char c;
int i;
};

and you pun it with:

struct test2{
char c[1];
char i[4];
};

then you are making the assumption that the layout is one byte for the char followed immediately by four bytes for the int. If you try this out on a 64-bit machine you will find that c[0] is the same as c, but i[4] doesn’t represent the integer. The reason is for the problem is that the struct is packed so that the int starts on a word boundary. So the layout is one byte for the char and then three bytes of padding to make the four bytes of the int start on a word boundary. A correct pun is:

struct test2{
char c[4];
char i[4];
};

with c corresponding to c[0] and the int corresponding to i[0] to i[3]. Clearly this is machine-dependent and there is no way you can gain access to the bytes that make up the int in a machine-independent way.

In Dive but not in this extract

  • Structs As Objects
  • The Array At The End Of The Struct
 


Last Updated ( Wednesday, 27 August 2025 )