Go — Dealing with maligned structs

Sebastián Segura
3 min readNov 9, 2018

--

Sometimes we find ourselves dealing with a linter error that states something like:

warning: struct of size 40 could be 32 (maligned)

So we search at Google, Stack Overflow, GitHub, official docs and other communities out there that may help us with this problem. However, the lack of information just leave us more confused and empty handed. There’s clearly something wrong with our code, so what exactly is it?

By oversimplifying things, we could say that the problem is that the order of the struct’s fields could be arranged differently for optimizing the amount of memory that needs to be allocated for the struct. But for appropriately ordering them we need to know some concepts and how they interact with each other:

  1. System’s architecture: how the size in bytes of words and the maximum alignment in memory are defined. One of the main differences between 32 and 64 bit systems is that the word size at the 32 bit systems is 4 bytes and 8 bytes at the 64 bit.
  2. Golang’s primitive type sizes: how many bytes each struct’s field needs in memory. You can check the different type sizes from the supported types here, also it contains an explanation on what the word size and architecture has to do with that.
  3. Memory allocation: how padding bytes are added for properly aligning fields in memory. Usually, types with a size equal or greater than the defined word size require to be aligned at offsets that are multiples of the word size.

Putting it all together, let’s assume we have a x64 system with a word size of 8 bytes and have a struct that looks something like this:

// A struct describing a Carstruct {    Doors  int32    Brand  string    IsNew  bool    Model  string}

According to the type sizes defined for a x64 architecture, we have the following:

// A struct describing a Carstruct {    Doors  int32     // 4 bytes    Brand  string    // 16 bytes    IsNew  bool      // 1 byte    Model  string    // 16 bytes}

Which will cause a memory allocation that will look like this:

Example memory allocation, generated with: http://golang-sizeof.tips/

Where we can see that padding (red squares) is added between the Doors and Brand fields in order to “complete” a word in memory, so the next field can start at an offset that’s multiple of a word size. At the previous example, we see that there are 11 bytes of padding that are just wasted, and this kind of waste is what the linters are warning us about.

In order to solve the problem from the example, we have to rearrange the struct’s fields in order to minimize the amount of padding bytes that are required for allocating the struct. Since the Doors and IsNew fields have a combined size less than a word size, they can be located right next to each other, reducing the padding bytes required from 11 to 3; so we end with something like this:

// A struct describing a Carstruct {    IsNew  bool      // 1 byte    Doors  int32     // 4 bytes    Brand  string    // 16 bytes    Model  string    // 16 bytes}

A memory allocation of:

Example memory allocation, generated with: http://golang-sizeof.tips/

And the linter error is gone! 🎉

Additional References

There’s a very useful tool online that gives a visual representation on how the memory is aligned for structs and how many bytes are wasted because of padding. This is what actually helped me solve these kind of problems. Check it out!.

Also, make sure to also check these other sources:

--

--

Sebastián Segura

Developer | Half human | Half sloth | Half addicted to coffee