垫一个 C++ 2 的幂结构

发布于 2024-08-01 17:12:55 字数 778 浏览 7 评论 0原文

我正在为嵌入式系统编写一些 C++ 代码。 代码使用的 I/O 接口要求每个消息的大小(以字节为单位)是 2 的幂。 现在,代码做了这样的事情(在几个地方):

#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,数组大小表达式下溢,编译器会抱怨数组太大。

理想情况下,我希望能够编写一个宏,该宏将获取结构的大小并在编译时评估填充数组所需的大小,以便将结构的大小舍入为 2 的幂。

我查看了 Bit Twiddling Hacks 页面,但我不知道是否其中的任何技术实际上都可以在宏中实现,以便在编译时进行评估。

这个问题还有其他解决方案吗? 或者我应该让这个问题永久存在,只是将神奇的 64 更改为神奇的 128?

I'm working on some C++ code for an embedded system. The I/O interface the code uses requires that the size of each message (in bytes) is a power of two. Right now, the code does something like this (in several places):

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

I'm trying to compile the code on a 64-bit Fedora for the first time, where long is 64-bits. In this case, sizeof(internal_) is greater than 64, the array size expression underflows, and the compiler complains that the array is too large.

Ideally, I'd like to be able to write a macro that will take the size of the structure and evaluate at compile time the required size of the padding array in order to round the size of the structure out to a power of two.

I've looked at the Bit Twiddling Hacks page, but I don't know if any of the techniques there can really be implemented in a macro to be evaluated at compile time.

Any other solutions to this problem? Or should I perpetuate the problem and just change the magical 64 to a magical 128?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

烟若柳尘 2024-08-08 17:12:55

还有另一个模板解决方案(从 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;
}

我的方法就是继续将填充大小加倍,直到它至少与字体大小一样大。

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.

摇划花蜜的午后 2024-08-08 17:12:55

我喜欢 Niki 的回答,尤其是这一部分具有匿名结构。

答案没有解决的一件事是大于 64 字节的问题,但是可以通过有条件地声明 char[128] 结构成员来解决,如果 sizeof(long)= =8 并声明 char[64] 否则。

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.

等待圉鍢 2024-08-08 17:12:55

基础知识

基于 Fizzer 的答案,但具有更现代的 (C++17) 风格,以及我认为的一个更好的界面,允许您通过以下方式填充任何结构/类:

#include "padded.h"

struct UnpaddedStruct
{
    unsigned char unaligned_data[5];
};

using PaddedStruct = Padded<UnpaddedStruct>;

sizeof(UnpaddedStruct) 是 5,如预期的那样。
sizeof(PlatedStruct) 是 8,正如所希望的那样。

这里最大的区别是没有特殊的数据成员(除了可能的填充),也不需要以任何特定的方式编写类来添加填充。 您只需使用 Plated 结构进行对齐,然后以与不使用填充时完全相同的方式访问您的结构。

这是一个现成的文件,您应该能够将其作为标头包含在内,以允许
对于该功能:

#ifndef PADDED_H
#define PADDED_H
/**
 * padded.h - Padding to align types with powers of two.
 */
#include <cstddef>
#include <cstdint>
#include <limits>

/**
 * pad_size_of<T>() - Get the size required to align T to a power of two.
 * @tparam T: The type to get alignment values for.
 *
 * Return: The amount of padding required to align T to a power of two.
 */
template <typename T>
constexpr size_t pad_size_of()
{
    // Edge case. The highest number which is representable for any size type
    // is going to be one less than the number we want to align it to.
    const size_t max_of = std::numeric_limits<size_t>::max();
    if constexpr(sizeof(T) > max_of / 2) {
            return max_of - sizeof(T) + 1;
    }
    // We want to find the power of two that can fit AT LEAST sizeof(T).
    size_t power_of_two = 1;
    // There's definitely some better way to do this, but this is the most
    // straight forward way to do what it's supposed to do. It's a constexpr
    // function, so there should be no runtime overhead anyway.
    while (power_of_two < sizeof(T)) {
        power_of_two *= 2;
    }
    // Finally, our padding size is simply the difference between the two.
    return power_of_two - sizeof(T);
}

/**
 * struct Padded<T, Size> - A struct including padding for alignment.
 * @tparam T: The class to add padding to.
 * @tparam Size: The amount of padding to add to the class.
 *
 * Simple struct which can be inherited to add padding to any class. Will
 * default to figuring out the amount of padding required to align a class to
 * a power of two, then use that.
 */
template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T
{
    /// Simple array of bytes large enough to align the class to powers of two.
    uint8_t padding[Size]{};
};


// Specialization of the above template for the base case, where no padding is
// required for alignment, so none is included.
template <typename T>
struct Padded<T, 0> : public T {};
#endif // PADDED_H

详细信息

对于特别感兴趣的人,让我们解释一下这里发生了什么。

首先,我知道有些人不喜欢注释/文档字符串,认为它们很吵,所以让我们从没有注释的纯代码开始:

#include <limits>

template <typename T>
constexpr size_t pad_size_of()
{
    const size_t max_of = std::numeric_limits<size_t>::max();
    if constexpr(sizeof(T) > max_of / 2) {
            return max_of - sizeof(T) + 1;
    }
    size_t power_of_two = 1;
    while (power_of_two < sizeof(T)) {
        power_of_two *= 2;
    }
    return power_of_two - sizeof(T);
}

template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T
{
    uint8_t padding[Size]{};
};

template <typename T>
struct Padded<T, 0> : public T {};

这里的第一个“魔法”是 constexpr 函数:

template <typename T>
constexpr size_t pad_size_of()

constexpr 函数可以在编译时求值,这意味着我们可以使用它们来计算,例如类属性。 我们不必滥用结构和枚举来做任何事情,我们只需写下我们的意思即可。

编译器可能会省略 constexpr 函数调用,而是直接使用函数调用的结果,而不是在运行时调用它。 这意味着不应该有二进制膨胀或任何东西,它只是编译器可以用来计算我们需要多少填充的简单函数。

然后,我们将 Plated 结构定义为模板类:

template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T

它需要两个模板参数,一个是要添加填充的类,另一个是要添加到其中的填充量。 默认情况下,它会在我们的 pad_size_of 函数的帮助下,根据类的大小自动计算出这一点,因此您通常不需要考虑这一点。 您可以考虑一下,例如,如果您想要对齐到sizeof(int)的倍数而不是2的幂,但在大多数情况下,2的幂应该是合理的案例。

最后,我们对 Plated 结构进行了特殊化,即:

template <typename T>
struct Padded<T, 0> : public T {};

这是另一个“神奇”位。 如果结构不需要填充,则它不会有填充。 简单如。 这允许您向底层结构添加数据成员,而不必担心更新任何填充值或其他内容。 如果不需要开销,就不会有开销。

完整示例

#include <cstdint>
#include <iostream>
#include "padded.h"

/**
 * struct UnpaddedStruct - Some arbitrary struct with no padding.
 */
struct UnpaddedStruct
{
    /// Whatever unaligned data you want.
    uint8_t data[5];
};

/**
 * struct Struct - An UnpaddedStruct with extra padding.
 */
using Struct = Padded<UnpaddedStruct>;


int main(int argc, char *argv[])
{
    Struct s{'a', 'b', 'c', '\0'};
    std::cout
            << "UNPADDED: " << sizeof(UnpaddedStruct)
            << ", PADDED: " << sizeof(Struct)
            << ", CONTENT: " << s.data
            << std::endl;
    return 0;
}

输出

UNPADDED: 5, PADDED: 8, CONTENT: abc

Basics

Basing myself on Fizzer's answer, but with a more modern (C++17) style, and what I consider to be a nicer interface, allowing you to pad any struct/class in the following way:

#include "padded.h"

struct UnpaddedStruct
{
    unsigned char unaligned_data[5];
};

using PaddedStruct = Padded<UnpaddedStruct>;

sizeof(UnpaddedStruct) is 5, as expected.
sizeof(PaddedStruct) is 8, as wanted.

The biggest difference here is that there's no special data members (aside from possibly padding), nor do you have to write your classes in any particular way to add padding. You simply use the Padded struct for alignment, and otherwise access your struct in the exact same way you would without padding.

Here's a ready-made file you should be able to include as a header to allow
for that functionality:

#ifndef PADDED_H
#define PADDED_H
/**
 * padded.h - Padding to align types with powers of two.
 */
#include <cstddef>
#include <cstdint>
#include <limits>

/**
 * pad_size_of<T>() - Get the size required to align T to a power of two.
 * @tparam T: The type to get alignment values for.
 *
 * Return: The amount of padding required to align T to a power of two.
 */
template <typename T>
constexpr size_t pad_size_of()
{
    // Edge case. The highest number which is representable for any size type
    // is going to be one less than the number we want to align it to.
    const size_t max_of = std::numeric_limits<size_t>::max();
    if constexpr(sizeof(T) > max_of / 2) {
            return max_of - sizeof(T) + 1;
    }
    // We want to find the power of two that can fit AT LEAST sizeof(T).
    size_t power_of_two = 1;
    // There's definitely some better way to do this, but this is the most
    // straight forward way to do what it's supposed to do. It's a constexpr
    // function, so there should be no runtime overhead anyway.
    while (power_of_two < sizeof(T)) {
        power_of_two *= 2;
    }
    // Finally, our padding size is simply the difference between the two.
    return power_of_two - sizeof(T);
}

/**
 * struct Padded<T, Size> - A struct including padding for alignment.
 * @tparam T: The class to add padding to.
 * @tparam Size: The amount of padding to add to the class.
 *
 * Simple struct which can be inherited to add padding to any class. Will
 * default to figuring out the amount of padding required to align a class to
 * a power of two, then use that.
 */
template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T
{
    /// Simple array of bytes large enough to align the class to powers of two.
    uint8_t padding[Size]{};
};


// Specialization of the above template for the base case, where no padding is
// required for alignment, so none is included.
template <typename T>
struct Padded<T, 0> : public T {};
#endif // PADDED_H

Details

For the especially interested, let's explain what's going on here.

First, I know some people dislike comments/docstrings finding them to be noisy, so let's start with the plain code with no comments:

#include <limits>

template <typename T>
constexpr size_t pad_size_of()
{
    const size_t max_of = std::numeric_limits<size_t>::max();
    if constexpr(sizeof(T) > max_of / 2) {
            return max_of - sizeof(T) + 1;
    }
    size_t power_of_two = 1;
    while (power_of_two < sizeof(T)) {
        power_of_two *= 2;
    }
    return power_of_two - sizeof(T);
}

template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T
{
    uint8_t padding[Size]{};
};

template <typename T>
struct Padded<T, 0> : public T {};

The first bit of "magic" here is the constexpr function:

template <typename T>
constexpr size_t pad_size_of()

constexpr functions can be evaluated at compile-time, which means we can use them to figure out, e.g. class properties. We don't have to abuse structs and enums to do whatever, we simply write what we mean.

A constexpr function call may be omitted by the compiler, instead using the result of the function call directly, rather than calling it at runtime. This means there should be no binary bloat or anything, it's just a simple function the compiler can use to figure out how much padding we need.

We then define our Padded struct as a template class:

template <typename T, size_t Size = pad_size_of<T>()>
struct Padded : public T

It takes two template arguments, one being the class you want to add padding to, and the other being the amount of padding you want to add to it. It defaults to figuring that out automatically for you based on the size of the class, with the help of our pad_size_of function, so that's usually not something you have to think about. You can think about it, if you want to e.g. align to a multiple of sizeof(int) rather than a power of two, but power of two should be reasonable in most cases.

Finally, we have a specialization of our Padded struct, namely:

template <typename T>
struct Padded<T, 0> : public T {};

This is the other "magical" bit. If a struct does not require padding, it will not have padding. Simple as. This allows you to e.g. add data members to the underlying struct without having to worry about updating any padding values or whatever. If no overhead is required, there will be no overhead.

Full Example

#include <cstdint>
#include <iostream>
#include "padded.h"

/**
 * struct UnpaddedStruct - Some arbitrary struct with no padding.
 */
struct UnpaddedStruct
{
    /// Whatever unaligned data you want.
    uint8_t data[5];
};

/**
 * struct Struct - An UnpaddedStruct with extra padding.
 */
using Struct = Padded<UnpaddedStruct>;


int main(int argc, char *argv[])
{
    Struct s{'a', 'b', 'c', '\0'};
    std::cout
            << "UNPADDED: " << sizeof(UnpaddedStruct)
            << ", PADDED: " << sizeof(Struct)
            << ", CONTENT: " << s.data
            << std::endl;
    return 0;
}

Output

UNPADDED: 5, PADDED: 8, CONTENT: abc
凯凯我们等你回来 2024-08-08 17:12:55

您可以使用模板获得结构大小四舍五入为 2 的幂的编译时常量:

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

这样填充值应该很容易。

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.

梦情居士 2024-08-08 17:12:55

如何在发送和接收消息函数周围编写一个小包装器来处理任何大小的消息,它们只分配一个更大的缓冲区(下一个 2 的幂)并 memclear 它,将结构复制到开头并一起发送。

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.

伊面 2024-08-08 17:12:55

使用模板元程序。 (根据评论进行编辑)。

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

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;
}
白云悠悠 2024-08-08 17:12:55

为什么不使用工会?

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

或者更好的是使用匿名结构,

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

这样你就可以像这样访问成员:Message.member1;

编辑:显然这并不能解决大于 64 的问题,但提供了一种更干净的填充方式。

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.

梅窗月明清似水 2024-08-08 17:12:55

也许最明显的方法是只使用三元运算符:

#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

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
∝单色的世界 2024-08-08 17:12:55

您已经在使用 #pragma pack,我不知道您具体使用什么编译器,但您应该查看它们是否支持控制对齐/填充的 pack 参数,然后您可以只需完全删除填充字段即可。 我知道 MSVC 的 版本 pragma pack 支持这一点,GCC 也是如此。

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.

末骤雨初歇 2024-08-08 17:12:55

解决该问题的一种方法是用大小(长)的倍数替换硬编码的 64,将填充变成如下所示:

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

它很丑,但应该可以移植到 64 位。

也就是说,要求消息大小为 2 的幂的 API 听起来有点奇怪,就像一个设计问题。 要求大小为偶数是有道理的,因为在某些处理器上访问奇数地址上的数据要付出相当大的代价,但你的 #pragma 包几乎使这种情况不可避免。

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.

安静 2024-08-08 17:12:55
union Message
{
    struct
    {
        unsigned long member1;
        unsigned long member2; //...
    };
    char pad[1 << 5]; //change 5 to whatever size you need...
};

会干净一点。

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.

各自安好 2024-08-08 17:12:55

宏化 (对于 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))

您可以如下 您可以使用 union 技巧或其他方法来应用它。 在你的例子中:

#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 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()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文