Дополните структуру C ++ степенью двойки - PullRequest
10 голосов
/ 06 августа 2009

Я работаю над кодом C ++ для встроенной системы. Интерфейс ввода / вывода, который использует код, требует, чтобы размер каждого сообщения (в байтах) был степенью двойки. Прямо сейчас, код делает что-то вроде этого (в нескольких местах):

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

Я впервые пытаюсь скомпилировать код на 64-битной Fedora, где long - 64-битная. В этом случае sizeof(internal_) больше 64, выражение размера массива теряет значение, и компилятор жалуется, что массив слишком велик.

В идеале я хотел бы иметь возможность написать макрос, который будет принимать размер структуры и оценивать во время компиляции требуемый размер массива заполнения, чтобы округлить размер структуры до степени два.

Я смотрел на страницу Bit Twiddling Hacks , но я не знаю, может ли какой-либо из методов действительно быть реализован в макросе, который будет оцениваться во время компиляции.

Есть ли другие варианты решения этой проблемы? Или я должен увековечить проблему и просто изменить магические 64 на магические 128?

Ответы [ 11 ]

15 голосов
/ 06 августа 2009

Используйте шаблонную метапрограмму. (Отредактировано в ответ на комментарий).

#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;
}
6 голосов
/ 06 августа 2009

Вероятно, наиболее очевидным способом было бы просто использовать троичный оператор:

#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
5 голосов
/ 06 августа 2009

Почему бы не использовать союз?

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

или, что еще лучше, использовать анонимные структуры

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

Таким образом, вы можете получить доступ к таким членам: Message.member1;

Редактировать: очевидно, что это не решит проблему, превышающую 64, но обеспечивает более чистый способ заполнения.

4 голосов
/ 06 августа 2009

Как насчет того, чтобы просто написать небольшую оболочку для функции отправки и получения сообщений, которая обрабатывает сообщения любого размера, и они просто выделяют больший буфер (следующая степень 2) и запоминают его, копируют структуру в начало и отправляют ее вместе.

4 голосов
/ 06 августа 2009

Один из способов обойти эту проблему - заменить жестко закодированный 64 кратным (длинным) размером, превратив отступ в нечто вроде этого:

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

Это некрасиво, но должно переноситься на 64-битную версию.

Тем не менее, API, который требует, чтобы размер сообщения был степенью 2, звучит немного странно и похоже на проблему дизайна. Требование, чтобы размер был четным числом, имеет смысл, так как некоторые процессоры платят довольно штраф за доступ к данным по нечетным адресам, но ваш пакет #pragma почти делает это неизбежным.

2 голосов
/ 06 августа 2009

Вы можете макросизировать это следующим образом (для 32-битной архитектуры):

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

Тогда вы можете применить это с помощью трюка союза или каким-либо другим способом. В вашем примере:

#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()
2 голосов
/ 06 августа 2009

Вы уже используете #pragma pack, я не знаю, какой компилятор (ы) вы используете конкретно, но вы должны увидеть, поддерживают ли они аргументы для pack, которые управляют выравниванием / заполнением, и тогда вы можете просто избавиться от заполнение поля в целом. Я знаю, MSVC версия pragma pack поддерживает это, как и GCC .

1 голос
/ 06 августа 2009

Вы можете получить постоянную времени компиляции для размера структуры, округленную до степени два, используя шаблоны:

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

В этом случае значение заполнения должно быть простым.

0 голосов
/ 06 августа 2009

И еще одно шаблонное решение (воровство от 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;
}

Мой подход заключается в том, чтобы просто удваивать размер отступа до тех пор, пока он не станет хотя бы таким же большим, как размер шрифта.

0 голосов
/ 06 августа 2009

Мне нравится Ответ Ники , особенно часть с анонимными структурами.

Единственное, что не удалось решить в ответе, - это проблема с размером более 64 байт, но это можно решить путем условного объявления члена структуры char [128] если sizeof (long ) == 8 и объявление char [64] в противном случае.

...