When are pad bytes copied - struct assignment, pass by value, other?

后端 未结 3 457
小蘑菇
小蘑菇 2021-01-03 02:55

While debugging a problem, the following issue came up. (Please ignore minor code errors; the code is just for illustration.)

The following struct is defined:

<
相关标签:
3条回答
  • 2021-01-03 03:11

    C11 will let you define anonymous structure and union members:

    typedef union box_t {
      unsigned char allBytes[theSizeOfIt];
      struct {
        uint32_t x;
        uint16_t y;
      };
    } box_t;
    

    That union would behave almost the same as before, you can access .x etc but the default initialization and assignment would change. If you always ensure that your variables are correctly initialized like this:

    box_t real_b = { 0 };
    

    or like this

    box_t real_a = { .allBytes = {0}, .x = 1, .y = 2 };
    

    All padding bytes should be correctly initialized to 0. This wouldn't help if your integer types would have padding bits, but at least the uintXX_t types that you have chosen will not have them by definition.

    gcc and followers implement this already as extension even if they are not yet completely C11.

    Edit: In P99 there is a macro to do that in a consistent way:

    #define P99_DEFINE_UNION(NAME, ...)                     \
     union NAME {                                           \
       uint8_t p00_allbytes[sizeof(union { __VA_ARGS__ })]; \
       __VA_ARGS__                                          \
     }
    

    That is the size of the array is determined by declaring an "untagged" union just for its size.

    0 讨论(0)
  • 2021-01-03 03:13

    As Christoph said, there are no guarantees regarding the padding. Your best bet is to not use memcmp to compare two structs. It works at the wrong abstraction level. memcmp works byte-wise at the representation, while you need to compare the values of the members.

    Better use a separate compare function that takes two structs and compares each member separately. Something like this:

    int box_isequal (box_t bm, box_t bn)
    {
        return (bm.x == bn.x) && (bm.y == bn.y);
    }
    

    For your bonus, the three objects are separate objects, they are not part of the same array and pointer arithmetic between them is not allowed. As function local variables, they are usually allocated on the stack, and because they are separate the compiler can align them in any way that is best, e.g. for performance.

    0 讨论(0)
  • 2021-01-03 03:30

    The value of padding bytes is unspecified (C99/C11 6.2.6.1 §6):

    When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values.

    See also footnote 42/51 (C99:TC3, C1x draft):

    Thus, for example, structure assignment need not copy any padding bits.

    The compiler is free to copy or not copy padding as it sees fit. On x86[1], my guess would be that 2 trailing padding bytes will be copied, but 4 bytes won't (which can occur even on 32-bit hardware as structures may require 8-byte alignment, eg to allow atomic reads of double values).

    [1] No actual measurements were performed.


    To expand on the answer:

    The standard doesn't make any guarantees where padding bytes are concerned. However, if you initialize an object with static storage duration, the chance is high that you'll end up with zeroed padding. But if you use that object to initialize another one via assignment, all bets are off again (and I'd expect trailing padding bytes - again, no measurements done - to be particularly good candidates to be omitted from copying).

    Using memset() and memcpy() - even when assigning to individual members, as this can invalidate padding as well - is a way to guarantee the values of padding bytes on reasonable implementations. However, in principle the compiler is free to change padding values 'behind your back' any time (which might be related to caching members in registers - wildly guessing again), which you probably can avoid by using volatile storage.

    The only reasonably portable workaround I can think of is to specify the memory layout explicitly by introducing dummy members of appropriate size while verifying with compiler-specific means that no additional padding is introduced (__attribute__ ((packed)), -Wpadded for gcc).

    0 讨论(0)
提交回复
热议问题