dv_
dv_

Reputation: 1297

Filling in padding bytes of a C struct in C++? (NOT concerning struct packing!)

I am using a library with a struct that has 3 officially documented members, but its implementation actually contains 4. The last one is a byte array that is used for padding. This is a common technique in C to stay ABI compatible: add a bunch of bytes at the end, and if later versions add members to the struct, this padding area is shrunk accordingly. In total, the size of the struct stays the same.

But now I have to use this struct in C++, and I am using it for a static const value, so I need to initialize it using an initializer list. So, like this:

static const foo_struct foo = { 1, 2, 3 };

Since this does not initialize the fourth value, GCC prints out:

warning: missing initializer for member ‘foo_struct::padding’ [-Wmissing-field-initializers]

A constructor syntax like foo(1,2,3) would not work, since this is a C struct. And setting it to {0} is no option either, since I must initialize the first three members.

Is there a C++11/C++14 conform way of dealing with this warning?

EDIT: Simply using { 1, 2, 3, 0 } might work, but is unstable, since the padding area is undocumented. Also, if future versions add a member, then it would sum up to 5 members, and the warning would return.

Upvotes: 2

Views: 1042

Answers (2)

Michaël Roy
Michaël Roy

Reputation: 6489

C++ was made with compatibility with C in mind...

Since the library structure is a C struct, pad the bytes exactly as you would do it in C. While the padding bytes are undocumented, they are part of the struct definition, using a fourth initializer (a zero) will be fine, and is needed. You actually have no choice but to fill the pad bytes to zero.

Example:

// in the library .h
struct foo_struct
{
    int first, second, third;
    unsigned char padding[256 - 3 * sizeof(int)];
};

// in your cpp

// your could do this:
static const foo_struct foo1 = { 1, ,2, 3, 0 }; // add 0, since it's needed.

// or if you really want to create this boiler plate... 

static const foo_struct foo2;   // note that it is initialized to zero at startup
                          // as all static variables are, unless a value is specified

static bool InitFoo2();
static bool fooInitialized = InitFoo2();  // it does waste some data space...
static bool InitFoo2() 
{
    p = const_cast<foo_struct*>(&foo2);
    memset(p, 0, sizeof(foo2));      // not needed in this particular case
                                         // but doesn't hurt to do be explicit.
    p->first = 6;
    p->second = 7;
    p->third = 42;
    return true;
}

// Top keep your code compatible with future version, you have no choice, 
// but to clear the padding bytes of any foo_struct before using the library.
//  Let's look at dynamic  allocation,  the required data space is the 
//  exact same size as for static memory allocation.
//
foo_struct* bar()
{
    // sizeof(foo_struct) = 256 bytes, padding MUST be reset, 
    // so your app is compatible with future versions of the library.
    //
    foo_struct* p = (foo_struct*)malloc(sizeof(foo_struct));
    if (p)
    {
        // By clearing the foo_struct this way, you do not ever need to 
        // the undocumented members.
        memset(p, 0, sizeof(*p));
        p->first = 6;
        p->second = 7;
        p->third = 42;
     }
     return p;
}

I'd personally go for this solution:

static const foo_struct foo = { 6, 7, 42, 0 };

Upvotes: 0

Nir Friedman
Nir Friedman

Reputation: 17724

You can just write a function like this:

template <class ... T>
constexpr foo_struct make_foo_struct(T ... t) {
    return foo_struct{t..., 0};
}

static const auto foo = make_foo_struct(1, 2, 3);

You don't need to disable any warnings. As a bonus, if another field is added to the struct, the warning will come back (because you'll then have 5 members, and you're only initializing 4). This is also convenient because if you are creating lots of foos, and a new field is added that you don't care about (say it's a boolean, that you always want to be true), you can change make_foo_struct to initialize it the way you want, without modifying all of the call sites.

You can of course write out the types and argument names in make_foo_struct instead of using T...; it makes things more explicit but also requiring more maintenance and less flexible.

If the padding is removed, this should just fail to compile, and again you would only need to fix this one function. But if you don't like that, another option is to locally silence the warning with a compiler pragma, just in the function.

template <class ... T>
constexpr foo_struct make_foo_struct(T ... t) {
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
    return foo_struct{t...};
    #pragma GCC diagnostic pop
}

And to hell with it, I'll give a third option. If you there are 3 named members, and their names are stable, and you want to simply initialize them and zero out the rest, you can do:

constexpr foo_struct make_foo_struct(int x, int y, int z) {
    foo_struct f{};
    f.x = x; f.y = y; f.z = z;
    return f;
}

The compiler should happily optimize this out.

Upvotes: 2

Related Questions