C++ Data Member Alignment and Array Packing

10,550

Solution 1

An array of objects is required to be contiguous, so there's never padding between the objects, though padding can be added to the end of an object (producing nearly the same effect).

Given that you're working with char's, the assumptions are probably right more often than not, but the C++ standard certainly doesn't guarantee it. A different compiler, or even just a change in the flags passed to your current compiler could result in padding being inserted between the elements of the struct or following the last element of the struct, or both.

Solution 2

It would definitely be safer to do:

sizeof(foo) * SOME_NUM

Solution 3

If you copy your array like this you should use

memcpy(pBuff,listOfFoos,sizeof(listOfFoos));

This will always work as long as you allocated pBuff to the same size. This way you are making no assumptions on padding and alignment at all.

Most compilers align a struct or class to the required alignment of the largest type included. In your case of chars that means no alignment and padding, but if you add a short for example your class would be 6 bytes large with one byte of padding added between the last char and your short.

Solution 4

I think the reason that this works because all of the fields in the structure are char which align one. If there is at least one field that does not align 1, the alignment of the structure/class will not be 1 (the alignment will depends on the field order and alignment).

Let see some example:

#include <stdio.h>
#include <stddef.h>

typedef struct {
    unsigned char a;
    unsigned char b;
    unsigned char c;
} Foo;
typedef struct {
    unsigned short i;
    unsigned char  a;
    unsigned char  b;
    unsigned char  c;
} Bar;
typedef struct { Foo F[5]; } F_B;
typedef struct { Bar B[5]; } B_F;


#define ALIGNMENT_OF(t) offsetof( struct { char x; t test; }, test )

int main(void) {
    printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo));
    printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar));
    printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B));
    printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F));
}

When executed, the result is:

Foo:: Size: 3; Alignment: 1
Bar:: Size: 6; Alignment: 2
F_B:: Size: 15; Alignment: 1
B_F:: Size: 30; Alignment: 2

You can see that Bar and F_B has alignment 2 so that its field i will be properly aligned. You can also see that Size of Bar is 6 and not 5. Similarly, the size of B_F (5 of Bar) is 30 and not 25.

So, if you is a hard code instead of sizeof(...), you will get a problem here.

Hope this helps.

Solution 5

For situations where stuff like this is used, and I can't avoid it, I try to make the compilation break when the presumptions no longer hold. I use something like the following (or Boost.StaticAssert if the situation allows):

static_assert(sizeof(foo) <= 3);

// Macro for "static-assert" (only usefull on compile-time constant expressions)
#define static_assert(exp)           static_assert_II(exp, __LINE__)
// Macro used by static_assert macro (don't use directly)
#define static_assert_II(exp, line)  static_assert_III(exp, line)
// Macro used by static_assert macro (don't use directly)
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)}
Share:
10,550

Related videos on Youtube

Adam Holmberg
Author by

Adam Holmberg

Updated on April 16, 2022

Comments

  • Adam Holmberg
    Adam Holmberg about 2 years

    During a code review I've come across some code that defines a simple structure as follows:

    class foo {
       unsigned char a;
       unsigned char b;
       unsigned char c;
    }
    

    Elsewhere, an array of these objects is defined:

    foo listOfFoos[SOME_NUM];
    

    Later, the structures are raw-copied into a buffer:

    memcpy(pBuff,listOfFoos,3*SOME_NUM);
    

    This code relies on the assumptions that: a.) The size of foo is 3, and no padding is applied, and b.) An array of these objects is packed with no padding between them.

    I've tried it with GNU on two platforms (RedHat 64b, Solaris 9), and it worked on both.

    Are the assumptions above valid? If not, under what conditions (e.g. change in OS/compiler) might they fail?

  • rmeador
    rmeador over 14 years
    not just safer, but clearer and gets rid of a magic number. +1
  • David Thornley
    David Thornley over 14 years
    It certainly wouldn't surprise me if a compiler decided it liked things on four-byte boundaries, and put a byte of padding at the end.
  • Adam Holmberg
    Adam Holmberg over 14 years
    Yes, I agree with that. I guess I was more trying to get at the padding and array organization. Thanks.
  • nschmidt
    nschmidt over 14 years
    this does not account for padding between array elements though.
  • nschmidt
    nschmidt over 14 years
    see my answer below. the safest way is to use sizeof(listOfFoos)
  • Jerry Coffin
    Jerry Coffin over 14 years
    @nschmidt: padding between array elements is not allowed in either C or C++.
  • nschmidt
    nschmidt over 14 years
    @Jerry Coffin: you are right. The standard requires arrays to be contiguous. Padding occurs only within the struct/class.
  • JHBonarius
    JHBonarius over 2 years
    I know this is an old question, but I'm wondering: where in the c++ standard is it stated that padding can be added to the end of the object, and not the beginning? I read that it must be contiguous and that an array new-expressing may assign more space then required, but I cannot find any info that e.g. that the array object and the first element have the same address. [basic.compound] note 4 say this, must it doesn't seem to be a requirement. The standard doesn't seem to give clear explicit guarantees.
  • Jerry Coffin
    Jerry Coffin over 2 years
    @JHBonarius: The current standard only gives this guarantee with respect to standard layout objects and non-bitfield members. The normative text is at [class.mem]/26: "If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member if that member is not a bit-field."