2

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.

dv_
  • 1,247
  • 10
  • 13
  • "This is a common technique in C to stay ABI compatible" - Not really. But it is a good technique to run into trouble when changing platform or implementation. And it has nothing to do with the ABI. Forget about that dirty hack and compile all code with the same A**P**I. – too honest for this site Sep 06 '17 at 18:13
  • And this question is not related to C apparently, but C++. – too honest for this site Sep 06 '17 at 18:15
  • https://stackoverflow.com/a/330867/669576 – 001 Sep 06 '17 at 18:16
  • It is if the C struct is filled with function pointers. For example, GLib uses this in its GDBusInterfaceVTable struct. – dv_ Sep 06 '17 at 18:16
  • @dv_ glib is part of the C implementation/environment. That has a much more detailed knowledge about the platform. That does not mean you should use the same techniques in application code. – too honest for this site Sep 06 '17 at 18:18
  • A (good) C api would have a function to initialise this structure. Does your library offer one? – Richard Hodges Sep 06 '17 at 18:18
  • I did not write that I want to use this approach in my own struct. I said: "I am using a library with a struct that has 3 officially documented members, but its implementation actually contains 4." – dv_ Sep 06 '17 at 18:19
  • @RichardHodges: For embedded systems, this is basically fine, but one should use a designated initialiser, better a macro for static variables. In C++ IIRC, there can be default initialisers with the declaration. – too honest for this site Sep 06 '17 at 18:19
  • For reference, here is an example of what I mean: https://github.com/GNOME/glib/blob/master/gio/gdbusconnection.h#L382 Also, yes, the library I am using is a C library. – dv_ Sep 06 '17 at 18:21
  • 2
    The `foo = {1,2,3}` is the correct way to do it. The missing fields will be zero initialized, IIRC. The warning is just a warning after all, and the proper way to get rid of it is with `-Wno-missing-field-initializers`. Trying to initialize `padding` without using its name and any future added fields without ever issuing a warning is futile. – rodrigo Sep 06 '17 at 18:26
  • `#pragma GCC diagnostic push` `#pragma GCC diagnostic ignored "-Wmissing-field-initializers"` `static const foo_struct foo { 1, 2, 3 };` `#pragma GCC diagnostic pop` – Artemy Vysotsky Sep 06 '17 at 18:29
  • It's interesting that the file you have linked only defines that structure, never references it. Are you sure you need to do this? – Richard Hodges Sep 06 '17 at 18:47
  • Note that I am not using GDBus, I just used this as an example of what I mean. But to answer your question, yes, this particular struct is used in GDBus. See for example https://developer.gnome.org/gio/stable/GDBusConnection.html#g-dbus-connection-register-object – dv_ Sep 06 '17 at 19:00

2 Answers2

2

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.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • Too bad the `#pragma` solution isn't cross platform. – Mark Ransom Sep 06 '17 at 18:43
  • @MarkRansom I mean, if you are doing cross platform work, it should just be a given that you have a file where you use the preprocessor to detect the compiler, and then define your own macros for ignoring warnings, force inlining, etc. – Nir Friedman Sep 06 '17 at 18:46
  • in this case (gdbus) , if the answer was just the last version of make_foo_struct, I'd +1 it. – Richard Hodges Sep 06 '17 at 18:46
  • The {0} also gives me warnings. Still, I like your approaches, particularly the last one. +1 from me, thanks. – dv_ Sep 06 '17 at 18:47
  • @dv_ Actually, sorry get rid of the 0. Just `foo_struct f{}` should value initialize everything to 0. – Nir Friedman Sep 06 '17 at 18:48
0

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 };
Michaël Roy
  • 6,338
  • 1
  • 15
  • 19