【游戏编程扯淡精粹】C++对象内存布局

【游戏编程扯淡精粹】C++对象内存布局

引言

CPU给GPU传递buffer,约定好buffer item的struct协议,基本上要求是packed struct

那么packed前后有什么区别呢?知道struct字段对齐,那具体又是怎么对齐的呢?

此外,虚表指针vptr是放在哪里的呢,怎么对齐呢?

稍微想一想,编译器肯定知道这些信息,搜一下就有了

工具

/d1reportAllClassLayout
/d1reportAllClassLayout – 转储对象内存布局 | Ofek 的 Visual C++ 东西

VS可视化插件
Struct Layout - Visual Studio Marketplace

g++ fdump-class-hierarchy // 略

问题清单

  1. packed struct前后对比?
  2. vptr放在哪里?占几个字节?

结论

本文以MSVC Win32为例,不同C++编译器仍然有差异,跨平台时要小心避坑

  • vfptr,虚表指针,占4字节,放在头部,并且随着基类继承下去
  • 按N字节对齐是struct的属性,默认行为和编译器有关,强制指定N可能会影响内存布局和读写性能
    • alignof(T)返回N,#pragma pack(N)指定N
  • 字段不对齐会严重影响字段读写速度
    • x86默认4字节对齐,并用padding填充来确保每个字段对齐,会占用更多内存
    • 使用#pragma pack(1)来紧密布局,未对齐的字段会有两倍的读写开销

编译器默认布局
在这里插入图片描述
强制紧密布局

后面三个属性被拆到两个字,导致读取需要2个时钟周期,也就是两倍的开销
在这里插入图片描述

vfptr

class A
{
  int m_a, m_b;
  virtual void cookie() {}
  virtual void monster() {}
};
 
class B : public A
{
  double m_c;
  virtual void cookie() {};
};

/d1reportAllClassLayout

关注点:

  • size,总共占据多少字节
  • vfptr,虚表指针,占4字节,MSVC放在头部,并且随着基类继承下去
  • 字段布局,如何对齐
  • 虚表结构,当前类的元信息,然后是虚函数指针列表
class A	size(12):
	+---
 0	| {vfptr}
 4	| m_a
 8	| m_b
	+---
A::$vftable@:
	| &A_meta
	|  0
 0	| &A::cookie
 1	| &A::monster
A::cookie this adjustor: 0
A::monster this adjustor: 0
class B	size(24):
	+---
 0	| +--- (base class A)
 0	| | {vfptr}
 4	| | m_a
 8	| | m_b
	| +---
  	| <alignment member> (size=4)
16	| m_c
	+---
B::$vftable@:
	| &B_meta
	|  0
 0	| &B::cookie
 1	| &A::monster
B::cookie this adjustor: 0

Struct Layout

可视化后更加清楚了

  • vfptr,虚表指针,占4字节,MSVC放在头部,并且随着基类继承下去
  • 对齐,为了B.double对齐,A末尾加了4字节的padding
    在这里插入图片描述
    在这里插入图片描述

packed struct

#ifdef __GNUC__
#define PACKED_STRUCT __attribute__((packed,aligned(1)))
#else
#define PACKED_STRUCT
#endif

struct PACKED_STRUCT gpuvec4 {
    float x, y, z, w;

    gpuvec4() = default;

    explicit gpuvec4(float v) : x(v), y(v), z(v), w(v) {}

    gpuvec4(float a, float b, float c, float d) : x(a), y(b), z(c), w(d) {}
};

struct PACKED_STRUCT gpumat4 {
    float data_[16];

    gpumat4() = default;
};

enum MaterialFlags {
    sMaterialFlags_CastShadow = 0x1,
    sMaterialFlags_ReceiveShadow = 0x2,
    sMaterialFlags_Transparent = 0x4,
};

constexpr const uint64_t INVALID_TEXTURE = 0xFFFFFFFF;

struct PACKED_STRUCT MaterialDescription final {
    gpuvec4 emissiveColor_ = { 0.0f, 0.0f, 0.0f, 0.0f };
    gpuvec4 albedoColor_ = { 1.0f, 1.0f, 1.0f, 1.0f };
    gpuvec4 roughness_ = { 1.0f, 1.0f, 0.0f, 0.0f };
    float transparencyFactor_ = 1.0f;
    float alphaTest_ = 0.0f;
    float metallicFactor_ = 0.0f;
    uint32_t flags_ = sMaterialFlags_CastShadow | sMaterialFlags_ReceiveShadow;
#pragma region maps
    uint64_t ambientOcclusionMap_ = INVALID_TEXTURE;
    uint64_t emissiveMap_ = INVALID_TEXTURE;
    uint64_t albedoMap_ = INVALID_TEXTURE;
    uint64_t metallicRoughnessMap_ = INVALID_TEXTURE;
    uint64_t normalMap_ = INVALID_TEXTURE;
    uint64_t opacityMap_ = INVALID_TEXTURE;
#pragma endregion maps
};

默认对齐

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四字节对齐

在这里插入图片描述

class Base
{
public:
    int a = 1;
    virtual void print() {};
};

class Derived : public Base {
public:
    int b = 3;
    virtual void print() {};
};

在这里插入图片描述
在这里插入图片描述

posted @   zoloypzuo  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示