I'll try not to be too pedantic.

> If we declare a structure variable without initializing it, like any other variable in C, it will be uninitialized at first and may contain random values.

Really, it's important to stress that as far as you are concerned, uninitialized things don't contain values, they only contain undefined behavior.

> But in complex programs, a structure can easily have 20 members and more.

Mostly in poorly designed programs.

> The only reason not to use it is if you are forced to work with a C89 compiler and can’t upgrade.

Or if you have a Vector2D and don't need to be constantly reminded what comes after x.

> But what if we want a struct to feel like a real C type?

It's already a real type. The reason people typedef structs is to save typing 7 characters.

#include <stdlib.h>

...

struct Vector2D *vec;
vec = malloc(sizeof(struct Vector2D));
if (sec == NULL) {
// handle allocation failure
}

I recommend using sizeof *vec here, instead of sizeof (struct Vector2D), it's much harder to screw up and mix up types in my experience.

> The memory is uninitialized so it is a good idea to initialize it to zero bytes. We can do this by calling the memset function with the pointer to our new struct, the initialization value 0, and the size of our structure:

Don't do this by calling memset. There's no guarantee that memsetting a pointer, float or double to all bytes zero will actually produce things which equal zero. On implementations where null pointers are not all bits zero (rare but they do exist) you will not get a null pointer.

> The great thing about this is that it allows us to omit to allocate structs with malloc on the heap.

Nothing stops you from using pointers to struct typed objects with automatic storage duration when calling functions:

struct Vector2D vec;
foo(&vec);

> This macro is useful to check if the compiler added any padding in between the members of the structure.

I would say this is hardly a use, more of a curiosity, really you shouldn't write code which relies on the presence or absence or width of padding, you can't even reliably store information in padding (the value is free to change due to you writing to an unrelated member). The only situation where this makes any sense is when using packed structs, and those almost never make sense except for some very specific circumstances.

> It can also be used to get the memory address of the structure if you only have the address of one of its members and know what type of struct it is a member of.

I haven't been able to gather agreement on whether this is something actually allowed by the C standard. There are two main schools of thought about this and one allows it, the other prohibits it, it's an extreme example of a total grey area in the C standard.

> This is used in some advanced code e.g. the OOP implementation of the Linux Kernel.

I think calling what Linux does OOP is misleading. The kernel just has lots of vtables, I disagree that OOP is just about vtables (or the effect they give).

> The most general reason is that one of the design goals of C was to be a language that can be implemented on as many hardware platforms as possible. Therefore the standard needs to be flexible to allow compilers to adapt the actual implementation to the specialties and quirks of their hardware respective platforms.

I mean, it's actually more about ABI than hardware. You can have two ABIs with different padding requirements on the same hardware platform. It just so happens that ABIs are themselves usually designed with hardware in mind.

> And here we already see the solution. A structure has to get enough trailing padding to align with its biggest data type.

While this is not a bad way of thinking about it, again, really, it's important to stress that while developing code in C, you should NOT be relying on this for anything other than performance optimisations for a particular platform.

> (e.g. because you want to map a file format or some hardware registers exposed in memory)

While using struct packing to deal with hardware registers is forgivable (although, rare, given that hardware registers will often likely be aligned the same as in the ABI), you really shouldn't use it for any file format you want to be portable outside a single machine. With modern compilers there's effectively no penalty to doing this properly (i.e. defining functions like uint_least32_t read32le(void *p) which read byte by byte and de-serialize the number using shifts and ORs). Yes I have tested this. Not only will your code not be cryptic and broken the moment you find yourself on a big endian machine, it also won't be unnecessarily portable for no good reason.

> Thankfully, C supports so-called bitfields.

You make it sound like an array/struct of bools or a bitfield are the only two options.