Live data from Hacker News

Structures in C: From Basics to Memory Alignment

abstractexpr.com

51–58 of 58 posts

Re: Structures in C: From Basics to Memory Alignment

#51

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 t…

You can have memset/memcmp work for individual structs by setting the padding yourself, and to make sure you do it right, on GCC/clang you can use -Wpadded. It's probably best to do that on a case-by-case basis, unless you're prepared to deal with a lot of compiler warnings/errors though.

    #pragma GCC diagnostic push
    #pragma GCC diagnostic error "-Wpadded"
    struct foo {
      uint8_t a;
      uint8_t _pad[3];
      uint32_t b;
    };
    #pragma GCC diagnostic pop

Re: Structures in C: From Basics to Memory Alignment

#52

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 t…

But if you change the type name, you are probably also doing a search and replace. In the end, the only justification for the latter rather than the former is that the pointer name is often a short name like vec , and so sizeof *vec is shorter than the alternative. That has advantages. If there is a typo in a short name, you're more likely to see it. I can easily miss the typo: struct Vector3D *vec = malloc(sizeof(st…

You'll get a compiler error though unless you, in the same edit-compile cycle, created a new variable with the same name.

You also can't search-replace a type name as easily, because other variables will have the same original type but not need to be updated.

Re: Structures in C: From Basics to Memory Alignment

#53

Earlier quoted context omitted.

But if you change the type name, you are probably also doing a search and replace. In the end, the only justification for the latter rather than the former is that the pointer name is often a short name like vec , and so sizeof *vec is shorter than the alternative. That has advantages. If there is a typo in a short name, you're more likely to see it. I can easily miss the typo: struct Vector3D *vec = malloc(sizeof(st…

You'll get a compiler error though unless you, in the same edit-compile cycle, created a new variable with the same name. You also can't search-replace a type name as easily, because other variables will have the same original type but not need to be updated.

This is a fair point. If we have *foo = malloc(sizeof *foo), we can search and replace for all occurrences of foo in that scope and replace them; that's always something which potentially makes sense.

However with *foo = malloc(sizeof (type)), it doesn't always make sense to be searching and replacing all occurrences of type, even if restricted to that scope. And, when type is a built-in type like char *, we cannot* be replacing all occurrences of it, even in that same scope where foo is active.

Types are embroiled in declaring multiple entities in the program, whereas a variable name declares exactly one thing; all other occurrences of the variable are references to that thing. Sometimes it makes sense to replace some of those occurences but not others, but that's neither here nor there: in *foo = malloc(sizeof *foo) you would never replace one foo without the other, even if some references to foo elsewhere in scope remain unedited for good reasons.

Basically you'd have to do something extremely absent-minded or silly to wreck *foo = malloc(sizeof *foo), whereas accidentally wrecking the correctness of *foo = malloc(sizeof (type)) in some way can happen under only a small lapse of mindfulness. Since the two sides refer to different identifiers, it makes sense to edit them separately: if you're renaming foo, you don't touch (type); if you're changing (type) then you don't touch foo (but have to update its declaration elsewhere). There is no obvious, trivial, easy-to-maintain consistency maintain that is localized just in that assignment; things go wrong because of material in separate places elsewhere.

Re: Structures in C: From Basics to Memory Alignment

#54

Earlier quoted context omitted.

> is there a way to memset the entire stack frame at the start of a function such that memcmp works as expected? I'd argue no simply because the value of the padding bytes is always unspecified. A compiler that sees such a `memset` is (IMO) perfectly free to not zero known padding bytes since it knows their value should not matter to the program. Compilers might not currently do that but you can already see this kind…

Compiles are free to not zero padding bytes if a struct is passed to memset but there are some situations where padding is not unspecified, for example if you do partial initialization of it.

Could you explain what you mean by partial initialization?

Re: Structures in C: From Basics to Memory Alignment

#55

Earlier quoted context omitted.

Compiles are free to not zero padding bytes if a struct is passed to memset but there are some situations where padding is not unspecified, for example if you do partial initialization of it.

Could you explain what you mean by partial initialization?

  = { .foo = bar };

Re: Structures in C: From Basics to Memory Alignment

#56

Earlier quoted context omitted.

You'll get a compiler error though unless you, in the same edit-compile cycle, created a new variable with the same name. You also can't search-replace a type name as easily, because other variables will have the same original type but not need to be updated.

This is a fair point. If we have *foo = malloc(sizeof *foo), we can search and replace for all occurrences of foo in that scope and replace them; that's always something which potentially makes sense. However with *foo = malloc(sizeof (type)), it doesn't always make sense to be searching and replacing all occurrences of type , even if restricted to that scope. And, when type is a built-in type like char *, we cannot*…

Then again, if we are initializing something being declared, we have:

   T *p = malloc(sizeof (T));
or

   T *p = malloc(sizeof *p)
In the former, there is internal consistency with the repetition of T.

In the latter, repetition of p.

In the former, you're not going to change one T without the other, just like in the latter you won't change one p without the other.

Assignments give advantage to the latter:

   p = malloc(sizeof (T));  // no local redundancy linking parts together.

Re: Structures in C: From Basics to Memory Alignment

#57

> 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…

Some targets also don't use natural alignment. AIX, for example, uses 4-byte alignment for doubles that aren't the first field in the struct. GCC has the -malign-natural and -malign-power options for dealing with this.

Re: Structures in C: From Basics to Memory Alignment

#58
I support a C codebase that, over the years, has been supported on 4 different processor architectures (Alpha, SPARC, x86, Itanium).

It's rather simple to maintain structures with the exact same size and alignment on all of the platforms. Use dummy variables, group all structure members by type, and put character fields last.

Put a comment to the right of every member that contains the expected byte offset of that member. At the end of the structure, #define the total expected structure size in bytes, because as part of your test suite you have test cases that assert structure sizes (and member alignments). It's a little extra work at the start, but once you have that habit it's really simple and quick.

   typedef struct example {
     int mbr1;    // 0
     int dummy;   // 4 - dummy variable, unused
     double mbr2; // 8
     double mbr3; // 16
   } example;
   #define S_EXAMPLE_BYTES 24
Post reply on HN