提问人:alfC 提问时间:11/7/2023 最后编辑:alfC 更新时间:11/17/2023 访问量:356
如何将类的大小控制为成员大小的倍数?
How to control the size of a class to be a multiple of the size of a member?
问:
我有以下课程,
struct employee {
std::string name;
short salary;
std::size_t age;
};
举个例子,在 Linux amd64 中,struct 的大小是 48 字节,std::string 的大小是 32,即不是倍数。
现在,我需要以跨平台的方式,使大小是(第一个成员)大小的倍数。employee
std::string
(例如,跨平台可能意味着 Linux amd64 和 Apple ARM。
那是。sizeof(employee) % sizeof(std::string) == 0
我尝试控制整个班级或成员的填充使用,但似乎要求是 2 的幂太严格了。alignas
然后我尝试在末尾添加一个数组。
尽管如此,我还是遇到了两个问题,首先,在编译时不同平台中数组的确切大小是多少,其次,没有添加另一个成员来搞砸类的良好聚合初始化。char
首先,我这样做:
struct employee_dummy {
std::string name;
short salary;
std::size_t age;
};
struct employee {
std::string name;
short salary;
std::size_t age;
char padding[(sizeof(employee_dummy)/sizeof(std::string)+1)*sizeof(std::string) - sizeof(employee_dummy)];
};
注意丑陋的虚拟类,我什至不知道逻辑是否正确。
对于第二个问题,我没有解决方案。 我可以这样做,但是我需要添加一个构造函数,该类将不是聚合,等等。
struct employee {
std::string name;
short salary;
std::size_t age;
private:
char padding[(sizeof(employee_dummy)/sizeof(std::string)+1)*sizeof(std::string) - sizeof(employee_dummy)];
};
如何使用标准或非标准机制控制结构的大小,并将类保留为聚合?
以下是根据经验解决这个问题的链接:https://cppinsights.io/s/f2fb5239
添加的注释:
我意识到,如果添加填充的技术是正确的,那么计算就更加困难了,因为虚拟类可能已经在添加填充,所以我必须考虑最后一个元素的偏移量。
在此示例中,我想成为第一个成员 () 的倍数:data
std::complex
struct dummy {
std::complex<double> a;
double b;
std::int64_t b2;
int c;
};
struct data {
std::complex<double> a;
double b;
std::int64_t b2;
int c;
char padding[ ((offsetof(dummy, c) + sizeof(c)) / sizeof(std::complex<double>) + 1)* sizeof(std::complex<double>) - (offsetof(dummy, c) + sizeof(c)) ];
};
请注意,现在的公式更糟。
答:
这是一个符合标准的版本,没有如果或但是。
template <template<std::size_t> class tmpl, std::size_t need_multiple_of>
struct adjust_padding
{
template <std::size_t n>
static constexpr std::size_t padding_size()
{
if constexpr (sizeof(tmpl<n>) % need_multiple_of == 0) return n;
else return padding_size<n+1>();
}
using type = tmpl<padding_size<0>()>;
};
像这样使用它:
template <std::size_t K>
struct need_strided
{
double x;
const char pad[K];
};
template <>
struct need_strided<0>
{
double x;
};
using strided = adjust_padding<need_strided, 47>::type;
现在的大小是 47 的倍数(当然正确对齐)。在我的电脑上是 376。strided
您可以按以下方式制作模板:employee
template <std::size_t K>
struct employee { ...
或使其成为模板的成员(而不是):double x
template <std::size_t K>
struct employee_wrapper {
employee e;
然后用作矢量元素。但无论哪种方式,都为 0 提供专业化。employee_wrapper
您可以尝试使用 C 样式数组代替 C 样式数组,并避免为 0 提供专用化,但当大小为 0 时,它可能会也可能不会被优化。 (C++20)可能会有所帮助。std::array
[[no_unique_address]]
请注意,类似的东西可能会溢出编译器的默认 constexpr 深度。adjust_padding<need_strided, 117>::type
评论
pad_
[[no_unique_address]]
char[K]
std::array<char, K>
pad_
鉴于注释提供的上下文,OP 的主要目标是创建一个大小为 大小的倍数的结构,以便出于遗留兼容性原因将此类结构数组视为具有一定步幅的数组。std::string
std::string
C++ 标准没有以这样一种方式定义内存的布局,即您可以安全地跨过对象数组,就好像它是其成员之一的数组一样。
这是由于填充、对齐要求以及访问越界内存或创建不指向对象开头的指针时的潜在未定义行为所致。
我尝试了这个JDoodle SizeAdjuster项目作为更简单的方法:
#include <iostream>
#include <string>
#include <type_traits>
// Original employee struct with `salary` as an int
struct employee {
std::string name;
int salary; // Changed from short to int
std::size_t age;
};
// SizeAdjuster to make the size of a struct a multiple of the size of a member
template <typename T, typename MemberT>
struct SizeAdjuster {
T data;
// Calculate padding needed to make the size of T a multiple of the size of MemberT.
char padding[(-sizeof(T)) % sizeof(MemberT)];
};
// Adjusted employee struct with padding
using employee_adjusted = SizeAdjuster<employee, std::string>;
int main() {
// Create an employee object with adjusted size, salary is now an int, so 50000 is valid.
employee_adjusted emp_adj = {{"John Doe", 50000, 30}};
// Access and display employee data
std::cout << "Name: " << emp_adj.data.name << std::endl;
std::cout << "Salary: " << emp_adj.data.salary << std::endl;
std::cout << "Age: " << emp_adj.data.age << std::endl;
// Display the size of the adjusted employee object
std::cout << "Size of employee: " << sizeof(employee) << std::endl;
std::cout << "Size of std::string: " << sizeof(std::string) << std::endl;
std::cout << "Size of employee_adjusted: " << sizeof(employee_adjusted) << std::endl;
// Check if the size of employee_adjusted is a multiple of the size of std::string
std::cout << "Is size of employee_adjusted a multiple of the size of std::string? "
<< (sizeof(employee_adjusted) % sizeof(std::string) == 0 ? "Yes" : "No") << std::endl;
return 0;
}
这将考虑到零大小数组的问题,并试图通过确保我们始终有一个定义良好的数组来避免未定义的行为。但是,即使进行了此调整,在对象数组中大步前进的行为(就好像它是对象数组一样)仍可能调用未定义的行为。padding
employee_adjusted
std::string
的目标是确保结构的大小是 的倍数。在给定的输出中:SizeAdjuster
employee_adjusted
std::string
Size of employee: 48
Size of std::string: 32
Size of employee_adjusted: 64
Is size of employee_adjusted a multiple of the size of std::string? Yes
这表示原始结构的大小为 bytes,大小为 bytes。
通过向结构体添加足够的填充来完成其工作,使总大小成为 的倍数,在本例中为 字节。由于是 的倍数,条件为真,这就是我们想要实现的。employee
48
std::string
32
SizeAdjuster
employee
std::string
64
64
32
sizeof(employee_adjusted) % sizeof(std::string) == 0
使大小的大小为 大小的倍数的目的是以这样一种方式对齐内存布局,即当您有一个对象数组时,理论上可以以一致的步幅直接访问该成员。在 OP 的上下文中,这是为了与期望这种内存对齐的遗留系统或框架兼容。employee_adjusted
std::string
employee_adjusted
name
但是,需要重申的是,虽然我们可以确保结构体的大小是 的倍数,但访问内存就像是数组一样,并不能保证安全或符合标准。该标准不支持将数组 视为数组,因为存在潜在的严格别名冲突和对齐问题。std::string
std::string
employee_adjusted
std::string
在实践中,该项目演示了如何计算和应用填充以实现大小要求,但它不认可或实现不安全的内存访问模式。
访问数组中每个成员的正确方法仍然是通过对象本身,而不是将内存视为 的数组。SizeAdjuster
name
employee_adjusted
employee
std::string
输出说明已实现尺寸调整,但未说明安全或符合标准的跨步访问,这超出了单独调整尺寸的能力。
如果您打算出于对齐目的计算填充,则只需将其应用于需要严格对齐的类型,这些类型通常是 POD(普通旧数据)类型。std::string 是一种具有非平凡构造函数和析构函数的类类型,因此通常不会以这种方式计算填充。
对于标准布局类型,通常只需要在成员之间填充以满足对齐要求。如果您使用的是将 std::string 作为成员的 employee 类型,编译器会为您处理对齐方式。如果出于某种原因(这种情况不常见)需要确保 employee 对象在内存边界上对齐,该边界是 std::string 大小的倍数,则可以手动对齐整个 employee 对象,而不是在其中插入填充。
若要确保员工结构与 std::string 的大小对齐,可以使用 alignas 说明符(C++11 及更高版本)来指定整个结构的对齐方式。通常使用最大成员的对齐要求,或与缓存行大小(如 64 字节)对齐,以避免在多线程环境中出现错误共享。
下面是如何使用 alignas 定义类的示例:
#include <string>
#include <iostream>
#include <cstddef>
// Class definition with specified alignment
struct alignas(alignof(std::max_align_t)) employee {
std::string name; // 'std::string' is typically aligned to the max_align_t
short salary;
std::size_t age;
// Padding is not manually needed because 'alignas' takes care of alignment
};
int main() {
// Check the size of the class
std::cout << "Size of std::string: " << sizeof(std::string) << std::endl;
std::cout << "Size of employee: " << sizeof(employee) << std::endl;
std::cout << "Alignment of employee: " << alignof(employee) << std::endl;
// Instantiate an employee to show the address
employee emp;
std::cout << "Address of emp: " << &emp << std::endl;
return 0;
}
在此代码中,employee 结构与 std::max_align_t 对齐,std::是对齐类型,保证在任何给定实现上都具有最严格(最大)的对齐要求。这样,您可以确保员工对象适当对齐,而无需手动计算填充。alignof 运算符返回类型参数的对齐要求。alignas 和 alignof 的使用使代码符合现代 C++ 实践并避免未定义的行为。
使员工成为模板的成员。
template <template<std::size_t> class Tmpl, std::size_t NeedMultipleOf>
struct adjust_padding {
// Finding the padding size without recursion
template <std::size_t N>
static constexpr std::size_t padding_size() {
for (std::size_t i = N;; ++i) {
if (sizeof(Tmpl<i>) % NeedMultipleOf == 0) {
return i;
}
}
}
using type = Tmpl<padding_size<0>()>;
};
// Struct to demonstrate padding adjustment
template <std::size_t K>
struct need_strided {
double x;
const char pad[K];
};
// Specialization for zero padding
template <>
struct need_strided<0> {
double x;
};
// Using adjust_padding to ensure size is a multiple of 47
using strided = adjust_padding<need_strided, 47>::type;
// Generalized employee struct wrapped in a template
template <std::size_t K>
struct employee_wrapper {
// Your employee struct goes here
// Example: employee e;
};
在这个版本的 @n. m. 可以作为 AI 的例子中,adjust_padding 结构利用了编译时循环,
评论
无需制作模板或列出其成员两次。TL;DR:你可以深入到简洁的代码,比如:employee
#pragma pack(1) // Prevent padding at the end.
struct employee_base {
std::complex<double> a;
};
using employee = PaddingHelper<employee_base>;
解释如下。
核心思想是(从 C++17 开始)您可以简单地从包含实际有效负载的类型派生,并保留聚合初始化语法(在 godbolt 上):
#pragma pack(1) // Prevent padding at the end of 'dummy'.
struct dummy {
std::complex<double> a;
double b;
std::int64_t b2;
bool c;
};
struct employee : dummy {
char padding[
(sizeof(dummy)/sizeof(decltype(dummy::a))+1)*sizeof(dummy::a)
- sizeof(dummy)]
= {0};
};
static_assert(sizeof(employee) % sizeof(decltype(dummy::a)) == 0);
int main(){
employee e{{41, 42, 43, false}};
}
这当然是非标准的,但 gcc、clang 和 MSVC 支持它。我们需要它来确保我们自己可以控制填充。#pragma pack
请注意,如果基类只有一个成员(或者其大小是第一个成员的倍数),则会增加额外的填充。为了解决这个问题,我们可以利用空基类优化(live on godbolt):
#pragma pack(1)
template <std::size_t size>
struct padding{
char pad[size] = {};
};
template <>
struct padding<0>{};
template <class BaseClass, class FirstMember>
constexpr auto GetPaddingSize()
{
constexpr auto Size =
(sizeof(BaseClass) % sizeof(FirstMember) == 0
? 0
: (sizeof(BaseClass)/sizeof(FirstMember)+1)*sizeof(FirstMember)
- sizeof(BaseClass));
return Size;
}
#pragma pack(1) // Prevent padding at the end of 'dummy'.
struct dummy {
std::complex<double> a;
};
struct employee : dummy, padding<GetPaddingSize<dummy, decltype(dummy::a)>()> {
};
static_assert(sizeof(employee) % sizeof(decltype(dummy::a)) == 0);
static_assert(sizeof(employee) == sizeof(decltype(dummy::a)));
int main(){
[[maybe_unused]] employee e{{41}};
}
由于您正在处理聚合类型,因此可以使用 boost::p fr
通过 (godbolt) 摆脱第一个成员的显式规范:boost::pfr::tuple_element_t<0, BaseClass>
#include <boost/pfr.hpp>
#pragma pack(1)
template <std::size_t size>
struct padding{
char pad[size] = {};
};
template <>
struct padding<0>{};
template <class BaseClass>
constexpr auto GetPaddingSize()
{
using FirstMember = boost::pfr::tuple_element_t<0, BaseClass>;
constexpr auto Size =
(sizeof(BaseClass) % sizeof(FirstMember) == 0
? 0
: (sizeof(BaseClass)/sizeof(FirstMember)+1)*sizeof(FirstMember)
- sizeof(BaseClass));
return Size;
}
//--------------------
#pragma pack(1) // Prevent padding at the end.
struct employee_base {
std::complex<double> a;
};
struct employee : employee_base, padding<GetPaddingSize<employee_base>()> {
};
static_assert(sizeof(employee) % sizeof(decltype(employee::a)) == 0);
static_assert(sizeof(employee) == sizeof(decltype(employee::a)));
//---------------------
#pragma pack(1) // Prevent padding at the end.
struct foo_base {
std::size_t i;
bool b;
};
struct foo : foo_base, padding<GetPaddingSize<foo_base>()> {
};
static_assert(sizeof(foo) % sizeof(decltype(foo::i)) == 0);
//---------------------
int main(){
[[maybe_unused]] employee e{{41}};
[[maybe_unused]] foo f{{41, false}};
}
然后你可以介绍
template <class BaseClass>
struct PaddingHelper : BaseClass, padding<GetPaddingSize<BaseClass>()> {};
允许写入,例如
#pragma pack(1) // Prevent padding at the end.
struct employee_base {
std::complex<double> a;
};
using employee = PaddingHelper<employee_base>;
见 godbolt。当然,你可以做类似的事情,而不需要,你只需要添加第二个模板参数来接收第一个成员类型。
我认为没有比这更简洁的了。boost::pfr
PaddingHelper
评论
operator new
sizeof
试试这个
struct employee {
std::string name;
union {
struct {
short salary;
std::size_t age;
};
std::string dummy;
};
};
在任何平台或编译器上,这应该是 2 * sizeof(std::string)。但以防万一,有人应该告诉我为什么不是这样,以及这会在哪个平台上失败!谢谢!
这可以很容易地推广到任何大小的数据字段的集合。只要有足够的 std::string 副本来覆盖原始结构中的总数大小,就可以在更改后的结构中强制执行 N * sizeof(std::string) 的大小,其中 N 是整数。
评论
std::string
dummy
评论
std::string