在C ++中填充C结构的填充字节? (不涉及结构包装!)

时间:2017-09-06 18:12:15

标签: c++ c++11

我正在使用一个带有3个官方文档成员的结构库,但它的实现实际上包含4.最后一个是用于填充的字节数组。这是C中保持ABI兼容的常用技术:最后添加一堆字节,如果以后的版本在结构中添加成员,则此填充区域会相应缩小。总的来说,结构的大小保持不变。

但是现在我必须在C ++中使用这个结构,并且我将它用于静态const值,所以我需要使用初始化列表初始化它。所以,像这样:

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

由于这不会初始化第四个值,GCC会输出:

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

像foo(1,2,3)这样的构造函数语法不起作用,因为这是一个C结构。并且将其设置为{0}也不是选项,因为我必须初始化前三个成员。

是否有符合C ++ 11 / C ++ 14标准的处理此警告的方式?

编辑:简单地使用{1,2,3,0}可能有效,但是不稳定,因为填充区域没有记录。此外,如果将来的版本添加一个成员,那么它总计最多5个成员,并且警告将返回。

2 个答案:

答案 0 :(得分:2)

你可以写一个这样的函数:

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);

您无需禁用任何警告。作为奖励,如果在结构中添加了另一个字段,警告将返回(因为您将有5个成员,而您只是初始化4)。这也很方便,因为如果你创建了很多foos,并且添加了一个你不关心的新字段(比如说它是一个布尔值,你总是想成为true),你可以改变{{ 1}}以你想要的方式初始化它,而无需修改所有的呼叫站点。

您当然可以在make_foo_struct中写出类型和参数名称,而不是使用make_foo_struct;它使事情更加明确,但也需要更多的维护和灵活性。

如果删除了填充,这应该无法编译,并且您只需要修复这一个函数。但是如果你不喜欢这样,另一个选择就是在函数中用编译器编译指示本地静音警告。

T...

对于地狱,我会给出第三种选择。如果您有3个命名成员,并且他们的名称是稳定的,并且您只想初始化它们并将其余部分归零,则可以执行以下操作:

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
}

编译器应该很乐意优化它。

答案 1 :(得分:0)

C ++是在兼容C的情况下制作的......

由于库结构一个C结构,填充字节的方式与在C中完全相同。虽然填充字节未记录,但它们是结构定义的一部分,使用第四个初始化程序(零)将是好的,是必要的。你实际上别无选择,只能将填充字节填充为零。

示例:

// 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;
}

我个人会选择这个解决方案:

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