gpt4 book ai didi

c++ - 打包类+继承的内存布局

转载 作者:行者123 更新时间:2023-12-03 12:20:59 26 4
gpt4 key购买 nike

考虑到您有两个类,Base和Derived,Derived是从Base公开继承的。

#include <iostream>

struct Base {
public:
unsigned char a;
int b;
Base() : a('a'), b (2) { };
unsigned char get_a() {
return a;
}
int get_b() {
return b;
}
} __attribute__ ((__packed__)) ;

struct __attribute__ ((__packed__)) Derived : public Base {
public:
unsigned char c;
int d;
Derived() : c('c'), d(4) { };

unsigned char get_c() {
return c;
}
int get_d() {
return d;
}
};

我正在做一些实验,这些是发现/问题。

首先,您不能在继承链中仅打包一个类。您必须打包链中的所有东西,否则就什么也没有。

其次,打包后,两个类的内存彼此相邻。首先是Base的内存,然后是Derived的内存。例如Derived的内存布局将是:Base的5个字节,然后Derived的下5个字节。是肯定/正确的吗?

第三,这与我的工作和IMO一个有趣的问题有关。
在我的流程中,直到确定的一点,我需要使用基类指针,并且在该特定指针之后,我将通过执行mem_copy或其他操作将所有基类指针转换为派生类指针。当然,我会添加“派生成员”的必要值。我怎样才能做到这一点?

要解决第三个问题,您将建议什么方法?如果没有虚拟,则由于虚拟会向内存增加4/8字节。

最佳答案

Packed属性是编译器特定的和非标准的。尽管如此,它已经存在了数十年,并且我使用的所有编译器(gcc,clang,icc,msvc,sun cc)都支持它,并且其行为符合预期:删除所有填充。

另一个考虑因素是,如果编译器无法如预期那样处理压缩属性(无论特定语法是什么),则编译器将破坏大量现有代码和基本代码,因此将无缘无故地限制其自身的可用性。

对于C++标准而言,标准化此功能将是理想的。我猜唯一的困难是,有人必须编写一份建议书并仔细阅读,以使其成为标准。

另一种选择是将每个成员以其自然的1字节对齐方式存储在unsigned char数组中。仅存在填充来满足对齐要求,并且无需填充以按1对齐:

#include <cstring>
#include <iostream>
#include <type_traits>

template<class T>
struct PackedMember {
static_assert(std::is_trivial<T>::value, "T must be a trivial type");
unsigned char storage_[sizeof(T)];

PackedMember() noexcept : storage_() // Zero-initialized.
{}

PackedMember(T const& t) noexcept {
set(t);
}

T get() const noexcept {
T t;
std::memcpy(&t, storage_, sizeof(T));
return t;
}

void set(T const& t) noexcept {
std::memcpy(storage_, &t, sizeof(T));
}
};

struct Base {
PackedMember<char> a = 'a';
PackedMember<int> b = 2;
};

struct Derived : Base {
PackedMember<char> c = 'c';
PackedMember<int> d = 4;
};

int main() {
Derived d;

std::cout << sizeof(d) << '\n';
std::cout << d.a.get() << '\n';
std::cout << d.b.get() << '\n';
std::cout << d.c.get() << '\n';
std::cout << d.d.get() << '\n';
}

输出:
10
a
2
c
4

这里的主要思想是删除填充,但保留成员名称。通过索引访问成员将不理想:难以理解代码;或必须创建索引的 enum并使用该代码使代码易于阅读。

在现代的x86-64 CPU上,这些 std::memcpy生成一条简单的 mov指令,就像对齐的成员一样。在这些CPU上,除非跨越了高速缓存行边界,否则处理未对齐访问的成本为0(自2011年 Sandy Bridge起)。如今,这是必须进行的CPU优化,可以在内存中存储和处理大型数据集,而不会浪费任何内存和高速缓存以进行填充,并且不会由于缺乏对齐而失去任何性能。我不熟悉其他CPU架构来对此发表评论。

关于c++ - 打包类+继承的内存布局,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57922848/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com