垫一个 C++ 2 的幂结构
我正在为嵌入式系统编写一些 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
还有另一个模板解决方案(从 fizzer 中大量窃取):
我的方法就是继续将填充大小加倍,直到它至少与字体大小一样大。
And yet another template solution (robbing hugely from fizzer):
My approach is simply to keep doubling the padding size until it's at least as big as the type size.
我喜欢 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.
基础知识
基于 Fizzer 的答案,但具有更现代的 (C++17) 风格,以及我认为的一个更好的界面,允许您通过以下方式填充任何结构/类:
sizeof(UnpaddedStruct)
是 5,如预期的那样。sizeof(PlatedStruct)
是 8,正如所希望的那样。这里最大的区别是没有特殊的数据成员(除了可能的填充),也不需要以任何特定的方式编写类来添加填充。 您只需使用
Plated
结构进行对齐,然后以与不使用填充时完全相同的方式访问您的结构。这是一个现成的文件,您应该能够将其作为标头包含在内,以允许
对于该功能:
详细信息
对于特别感兴趣的人,让我们解释一下这里发生了什么。
首先,我知道有些人不喜欢注释/文档字符串,认为它们很吵,所以让我们从没有注释的纯代码开始:
这里的第一个“魔法”是
constexpr
函数:constexpr 函数可以在编译时求值,这意味着我们可以使用它们来计算,例如类属性。 我们不必滥用结构和枚举来做任何事情,我们只需写下我们的意思即可。
编译器可能会省略 constexpr 函数调用,而是直接使用函数调用的结果,而不是在运行时调用它。 这意味着不应该有二进制膨胀或任何东西,它只是编译器可以用来计算我们需要多少填充的简单函数。
然后,我们将
Plated
结构定义为模板类:它需要两个模板参数,一个是要添加填充的类,另一个是要添加到其中的填充量。 默认情况下,它会在我们的
pad_size_of
函数的帮助下,根据类的大小自动计算出这一点,因此您通常不需要考虑这一点。 您可以考虑一下,例如,如果您想要对齐到sizeof(int)
的倍数而不是2的幂,但在大多数情况下,2的幂应该是合理的案例。最后,我们对
Plated
结构进行了特殊化,即:这是另一个“神奇”位。 如果结构不需要填充,则它不会有填充。 简单如。 这允许您向底层结构添加数据成员,而不必担心更新任何填充值或其他内容。 如果不需要开销,就不会有开销。
完整示例
输出
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:
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:
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:
The first bit of "magic" here is the
constexpr
function: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: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 ofsizeof(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: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
Output
您可以使用模板获得结构大小四舍五入为 2 的幂的编译时常量:
这样填充值应该很容易。
You can get a compile-time constant for the size of the structure rounded up to a power of two using templates :
The padding value should then be easy.
如何在发送和接收消息函数周围编写一个小包装器来处理任何大小的消息,它们只分配一个更大的缓冲区(下一个 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.
使用模板元程序。 (根据评论进行编辑)。
Use a template metaprogram. (Edited in response to comment).
为什么不使用工会?
或者更好的是使用匿名结构,
这样你就可以像这样访问成员:Message.member1;
编辑:显然这并不能解决大于 64 的问题,但提供了一种更干净的填充方式。
Why not use a union?
or better yet use anonymous structs
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.
也许最明显的方法是只使用三元运算符:
Probably the most obvious way would be to just use the ternary operator:
您已经在使用
#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 ofpragma pack
supports this, as does GCC's.解决该问题的一种方法是用大小(长)的倍数替换硬编码的 64,将填充变成如下所示:
它很丑,但应该可以移植到 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:
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.
会干净一点。
Would be a little cleaner.
宏化 此(对于 32 位架构):
您可以如下 您可以使用 union 技巧或其他方法来应用它。 在你的例子中:
You can macroize this as follows (for 32-bit architecture):
Then you can apply that using the union trick or some other means. In your example: