Live data from Hacker News

Structures in C: From Basics to Memory Alignment

abstractexpr.com

11–20 of 58 posts

Re: Structures in C: From Basics to Memory Alignment

#11

Earlier quoted context omitted.

All of these sound weird to me—most non-stupid (hello 802.2) protocols and hardware are going to have natural-aligned structure fields, so basically any mainstream (8-bit-byte, two’s complement, etc.) ABI is going to lay them out the same way, packed or not. As for RV64 and Arm64, the layout rules for same-size scalar types in their common ABIs are outright identical aren’t they? We’re (most of us) a long way away fr…

You assume it was RV64. It could have easily been RV32

I don’t think RV32 actually differs re alignment or struct layout, it’s just that with RV64 and Arm64 even the non-fixed-width names for the integer types are the same (LP64) except for Windows-on-ARM.

Re: Structures in C: From Basics to Memory Alignment

#12
Good article. 2 suggestions:

1. you mention that passing by value may be faster. The Vector2D function would be a good example for this, because the floats may be passed through registers instead of memory in certain ABIs. It’s a common mistake in linear algebra libraries. It also creates pure functions which leads to nicer to use APIs.

2. memset is strictly speaking not correct due to padding and null pointers not necessarily being 0. The newer = {} syntax solves this.

Re: Structures in C: From Basics to Memory Alignment

#13

  ...
  } __attribute__((packed));
I would encourage the placement of attributes before the objects they apply to. This has been allowed by GCC since the standardized attribute syntax was added to C++. It is a more natural fit for code that will eventually be upgraded to C23 attribute syntax in the future and is more prominently visible to a reader. It also avoids the sometimes awkward GCC rules for postfix attributes.

Re: Structures in C: From Basics to Memory Alignment

#14
post #6

Earlier quoted context omitted.

> All of these sound weird to me—most non-stupid (hello 802.2) protocols and hardware are going to have natural-aligned structure fields, so basically any mainstream (8-bit-byte, two’s complement, etc.) ABI is going to lay them out the same way, packed or not. In the long ago year of 2015 I worked on a project where the same binary packet was: 1. Generated by an 8 bit micro controller 2. Consumed by a 32bit Cortex M3…

> The phrase "natural aligned" has no meaning in that context. The phrase “naturally aligned” as I’m accustomed to seeing it used refers to the alignment of a power-of-two-sized type (usually a scalar one) being equal to its size. Unless you’re working with, say, 18-bit or 24-bit integers (that do exist in obscure places), it does have a meaning, and unless you’re using non-eight-bit bytes that meaning is fairly univ…

I think we agree that this makes sense on some metaphysical level. The problem is that there are definitely platforms where the normal alignment isn't what you describe above. And there isn't to my knowledge a switch in GCC to force it to follow these rules on any given platform. There isn't __attribute__((natural_alignment)). But there is __attribute__((packed)).

Re: Structures in C: From Basics to Memory Alignment

#15
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 
    
    ...
    
    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.

Re: Structures in C: From Basics to Memory Alignment

#16
> In C99 it is allowed to declare the last member of a structure as an array with no number of elements specified. The size of the struct will then be as if the last member did not exist.

There's one gotcha here, which is that the alignment requirements of the flexible array member can change the size of the struct. For example the following fails on x86_64-linux-gnu:

    struct flexible_char {
        char c;
        char arr[];
    };
    
    struct flexible_int {
        char c;
        int arr[];
    };
    
    _Static_assert(sizeof(struct flexible_char) == sizeof(struct flexible_int), "size mismatch");

Because `struct flexible_int` needs to be 4-byte aligned but `struct flexible_char` only needs to be 1-byte aligned.

Re: Structures in C: From Basics to Memory Alignment

#17
> The only good reason to use packed structures is when you need to map some memory (e.g. hardware registers exposed to memory) bit by bit to a structure.

Although unaligned access isn't fast especially if it's not directly hardware supported, it's still much faster than all the options where there just isn't enough memory.

It's... unfortunate that C doesn't standardize any of this.

Re: Structures in C: From Basics to Memory Alignment

#18
Good article, a couple nits/additional notes:

1. The article points out you should compare structs field by field, but it doesn't explain why memcmp wouldn't work. The reason is that the padding between the fields might not necessarily be zeroed in all cases. Field by field comparison is resilient to this.

2. The article proposes this for dynamic allocation:

    struct Vector2D *vec = malloc(sizeof(struct Vector2D));
I think it's better to use the variable name inside sizeof, so like this:

    struct Vector2D *vec = malloc(sizeof(*vec));
This helps you in the case where you change the type of the variable to different kind of struct. If you change the variable name, you're probably doing a find/replace anyway, and it will almost certainly fail to compile even if you miss it.

Re: Structures in C: From Basics to Memory Alignment

#19

> The only good reason to use packed structures is when you need to map some memory (e.g. hardware registers exposed to memory) bit by bit to a structure. Another common reason is when two CPUs of different architecture need to access the same structure in memory. E.g. you have a RiscV and an Arm64 processor in the same system, sharing memory. Or you read structured binary data from disk and need to specify an exact…

"The only good reason to "

The word opinionated was coined and adopted in English to describe a certain attitude. It has functioned fine for (probably) centuries (who knows, and I can't be bothered to research too far). Then came the age of IT and blow me, are we not opinionated to the point of ridiculousness.

A sentence construction along the lines of "The only good reason to" [do x] "is" [y], seems to invite a negation, quite aggressively. You might as well stand in the rain, wearing steel armour, and holding a long copper rod ... and shout "All Gods are bastards" (as a Knight of the Realm, sadly deceased, from hereabouts suggested might be an unwise life shortening decision).

I'm pretty sure packed structures have other uses.

Re: Structures in C: From Basics to Memory Alignment

#20

pretty cool, love anything related to C. might want to add anonymous struct. also put function pointers inside struct for simple object-oriented-programming in C. flexible array is handy, you do one malloc for all, but pointers inside struct is more 'flexible', for example you can put a 'void *' and cast it to various data types. for flexible array, the data types must be chosen first.

>but pointers inside struct is more 'flexible', for example you can put a 'void *' and cast it to various data types.

But then the array and other members won't be next to each other in memory.

Post reply on HN