Pad a C++ structure to a power of two

限于喜欢 提交于 2019-12-04 02:31:35

Use a template metaprogram. (Edited in response to comment).

#include <iostream>
#include <ostream>
using namespace std;

template <int N>
struct P
{
    enum { val = P<N/2>::val * 2 };
};

template <>
struct P<0>
{
    enum { val = 1 };
};

template <class T>
struct PadSize
{
    enum { val = P<sizeof (T) - 1>::val - sizeof (T) }; 
};

template <class T, int N>
struct PossiblyPadded
{
    T       payload;
    char    pad[N]; 
};

template <class T>
struct PossiblyPadded<T, 0>
{
    T       payload;
};

template <class T>
struct Holder : public PossiblyPadded<T, PadSize<T>::val>
{
};


int main()
{
    typedef char Arr[6];

    Holder<Arr> holder;
    cout << sizeof holder.payload << endl;

    // Next line fails to compile if sizeof (Arr) is a power of 2
    // but holder.payload always exists
    cout << sizeof holder.pad << endl;
}

Probably the most obvious way would be to just use the ternary operator:

#define LOG2_CONST(n) ((n) <= 1 ? 0 :
                      ((n) <= 2 ? 1 :
                      ((n) <= 4 ? 2 :
                      /* ... */
                      ))))))))))))))))))))))))))))))
#define PADDED_STRUCT(ResultName, BaseName) \
  typedef union { BaseName data; char pad[1 << LOG2_CONST(sizeof(BaseName))]; } ResultName

Why not use a union?

union Message
{
    struct internal_
    {
        unsigned long member1;
        /* more members */
    };
    char[64];
};

or better yet use anonymous structs

union Message
{
    struct
    {
        unsigned long member1;
        /* more members */
    };
    char[64];
};

So you can access members like this: Message.member1;

Edit: obviously this doesn't solve your greater than 64 problem, but provides a cleaner way of padding.

One way of working around the problem would be to replace the hardcoded 64 with a multiple of size(long), turning the padding into something like this:

char pad[4*sizeof(unsigned long) - sizeof(internal_)];

It's ugly but it should be portable to 64-bit.

That said, an API that requires the message size to be a power of 2 sounds a bit odd and like a design issue. Requiring the size being an even number makes sense as on some processors to pay quite a penalty for accessing data on odd addresses but your #pragma pack almost makes that inevitable.

How about just writing a small wrapper around the send and receive message function that handle any size message and they just allocate a larger buffer ( next power of 2 ) and memclear it, copy the struct to the beginning and send it along.

You're already using #pragma pack, I don't know what compiler(s) your using specifically, but you should see if they support arguments for pack that control the alignment/padding and then you can just get rid of the padding field altogether. I know MSVC's version of pragma pack supports this, as does GCC's.

You can macroize this as follows (for 32-bit architecture):

#define align_step(N, shift) ((N) | ((N) >> shift))
#define align_up(N) (align_step(align_step(align_step(align_step(align_step((N)-1, 1), 2), 4), 8), 16) + 1)
#define alignment_padding(N) (align_up((N)) - (N))

Then you can apply that using the union trick or some other means. In your example:

#pragma pack(1)
struct Message
{
   struct internal_
   {
      unsigned long member1;
      unsigned long member2;
      unsigned long member3;
      /* more members */
   } internal;
   char pad[alignment_padding(sizeof(internal_))];
};
#pragma pack()

You can get a compile-time constant for the size of the structure rounded up to a power of two using templates :

template<int N, int C = 1>
struct Recurse
{
    enum {result = Recurse<N/2, C*2>::result};
};

template<int C>
struct Recurse<0, C>
{
    enum {result = C};
};

template<typename T>
struct Calc
{
    enum {size = Recurse<sizeof(Test)-1>::result};
};

struct Test
{
    int a;
    double b;
    double c;
};

int main()
{
    std::cout << sizeof(Test) << " -> " << Calc<Test>::size << std::endl;
    return 0;
}

The padding value should then be easy.

union Message
{
    struct
    {
        unsigned long member1;
        unsigned long member2; //...
    };
    char pad[1 << 5]; //change 5 to whatever size you need...
};

Would be a little cleaner.

azheglov

I like Niki's answer, especially the part with anonymous structs.

One thing that answer didn't solve was the greater-than-64-bytes problem, but that can be solved by conditionally declaring a char[128] struct member if sizeof(long)==8 and declaring char[64] otherwise.

Richard Corden

And yet another template solution (robbing hugely from fizzer):

#include <iostream>
#include <ostream>
using namespace std;

template <int TSize, int PSize = 1, bool = false>
struct PadSize
{
  static const int val =
    ( PadSize <    TSize, (PSize*2), (TSize <= (PSize*2)  )    > :: val );
};

template < int TSize, int PSize>
struct PadSize <TSize, PSize, true>  // As soon as TSize is <= to PSize
{
  static const int val = PSize;
};

int main()
{
    typedef char Arr[8];
    char pad[ PadSize <sizeof(Arr)>::val  ];

    cout << sizeof pad << endl;
}

My approach is simply to keep doubling the padding size until it's at least as big as the type size.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!