如何对齐派生类成员,使第一个成员紧跟在基类的最后一个成员之后?

时间:2017-09-27 18:09:41

标签: c++ visual-c++ alignment memory-alignment

首先,我使用的是VC ++ 2015 U3,我正在寻找一个特定于该编译器的答案。

我有一个结构,其最后一个成员是char data[1],它是嵌入数据的占位符。我想让它变得更干净,只需从中获取一个模板结构,该模板结构需要一个size_t来表示它后面有多少数据。像这样:

// This is a placeholder for any WINAPI structs that has this form
struct base_struct
{
  DWORD stuff;
  char data[1];
};

template <size_t DATA_SIZE>
struct my_struct : base_struct
{
  char rest_of_data[DATA_SIZE - 1];
};

然而,存在对齐问题。 rest_of_data与基类的data相距4个字节。这很烦人。我已经尝试使用#pragma pack(push,1)/#pragma pack(pop), __declspec(align(1)),但我似乎无法让派生数据立即跟随基础数据。

也许我没有正确使用它们?有没有办法做到这一点?

P.S。,我已经知道我可以做很多技巧来管理内存并使用reinterpret_cast,但我想先看看这是否可行。

P.P.S。 base_struct不是我的结构。任何数量的结构都是WINAPI的一部分。我不应该更新或修改它,这就是为什么我试图制作一些我可以在已经定义的结构之上构建的东西。

修改

我尝试使用has-a而不是is-a关系,我发生了一些奇怪的事情。 offsetof()表示位置是正确的,但是当我得到实际的地址位置时,它是对齐的。 叹息

#include <iostream>
#include <windows.h>

#define report_success(x) (!(x) ? (void)0 : (void)(std::cout << #x << std::endl))
int main()
{
    struct old_t
    {
        DWORD                      stuff;
        CHAR                       data[1];
    };
    struct new_t
    {
        old_t __declspec(align(1)) internal;
        char  __declspec(align(1)) extended_data[1];
    };
    new_t x;

    ///////////////////////////////////////////////////////////////////////////// padded //////////// not padded /////
    //////////////////////////////////////////////////////////////////////// should | actually || should | actually //
    report_success(offsetof(new_t, extended_data) == sizeof(old_t) + 0);   //   T   |     T    ||    T   |     T    //
    report_success(offsetof(new_t, extended_data) == sizeof(old_t) + 1);   //   F   |     F    ||    F   |     F    //
    report_success(offsetof(new_t, extended_data) == sizeof(old_t) + 2);   //   F   |     F    ||    F   |     F    //
    report_success(offsetof(new_t, extended_data) == sizeof(old_t) + 3);   //   F   |     F    ||    F   |     F    //
    report_success(&x.extended_data[0] == &x.internal.data[1]);            //   T   |     F    ||    T   |     F    //
    report_success(&x.extended_data[0] == &x.internal.data[2]);            //   F   |     F    ||    F   |     F    //
    report_success(&x.extended_data[0] == &x.internal.data[3]);            //   F   |     F    ||    F   |     F    //
    report_success(&x.extended_data[0] == &x.internal.data[4]);            //   F   |     T    ||    F   |     T    //
    return 0;
}

预期产出:

offsetof(new_t, extended_data) == sizeof(old_t) + 0
&x.extended_data[0] == &x.internal.data[1]

实际输出:

offsetof(new_t, extended_data) == sizeof(old_t) + 0
&x.extended_data[0] == &x.internal.data[4]

Demo

2 个答案:

答案 0 :(得分:0)

简短的回答是,你不能做你想要的。编译器可以根据需要在.retry(new BiPredicate<ServerResponse, Throwable>() { @Override public boolean test(ServerResponse serverResponse, Throwable throwable) throws Exception { return serverResponse.getisVerified.equals("no"); } }) base_struct之间插入填充,你不能告诉它不要这样做。

如果您要使用模板,请考虑传递模板参数以指定所需的my_struct类型,而不是使用data,例如:

char[1]

然后定义要使用的template <typename T> struct base_struct { ... T data; }; 类型,例如:

data

或者,在C ++ 11及更高版本中:

template <size_t DATA_SIZE>
struct data_buffer
{
    char buffer[DATA_SIZE];
};

然后你可以使用template <size_t DATA_SIZE> using data_buffer = char[DATA_SIZE]; ,例如:

base_struct<data_buffer<...> >

或者,至少指定template <size_t DATA_SIZE> struct my_struct : base_struct<data_buffer<DATA_SIZE> > { }; 的完整大小作为模板参数:

base_struct::data[]

<强>更新

  

template <size_t DATA_SIZE> struct base_struct { ... char data[DATA_SIZE]; }; template <size_t DATA_SIZE> struct my_struct : base_struct<DATA_SIZE> { }; 不是我的结构。任何数量的结构都是base_struct

的一部分

在这种情况下,请考虑使用类似于NMHDR及其后代(NMLISTVIEWNMTREEVIEW等)的方法。 WINAPI 包含 my_struct成员,而不是base_struct派生 my_struct。然后,您可以将其他数据成员对齐base_struct成员的末尾:

base_struct

答案 1 :(得分:0)

看起来我能做的最好的事情就是使用一个模板结构,该结构包含一个包含旧类型的匿名联合和一个带有填充和实际数据的匿名结构。

template <typename OLD_T, size_t DATA_START, size_t SIZE>
struct new_t
{
    union
    {
        OLD_T internal;
        struct
        {
            char  __declspec(align(1)) padding[DATA_START];
            char  __declspec(align(1)) extended_data[SIZE];
        };
    };

    operator OLD_T&() { return internal; }
    OLD_T* operator&() { return &internal; }
};

new_t<old_t, offsetof(old_t, data), 5> object_with_5_char_array_at_end;

此处,extended_data实际上与data位于同一位置。我可以把它放在它之后按照要求回答这个问题,但是匿名结构实际上并没有被使用,所以我选择了更清晰的计算。

这并没有使用#pragma pack,因此整个结构可以正确对齐以提高速度。不幸的是,我必须有一个间接级别来访问原始对象,但至少我可以定义转换运算符,所以我至少可以传递它,好像它是原始对象类型和addressof运算符所以我可以将原始类型的地址作为原始类型指针。

太糟糕了,联盟无法从结构中继承以删除额外的间接层。当然,这可能会打开另外一堆蠕虫。 :)