第3章:数据语义

对齐原书第3章 The Semantics of Data:成员绑定与布局、静态和非静态访问、继承数据表示、对象效率及数据成员指针。

学习目标

  • 能解释 data member 的名称绑定、布局定位与实际 load/store 是三个相互衔接但不同的阶段
  • 能推导 inheritance without polymorphism、adding polymorphism、multiple inheritance 和 virtual inheritance 下的数据访问路径
  • 能分析 static/nonstatic data access、object member efficiency 与 pointer to data members 的表示和成本

从“成员名”和“成员地址”不是一回事开始

object.value 时,compiler 先根据 static type 做 member lookup 与 access control,再根据 object model 找到对应 storage。先预测 derived class 声明同名 member 后,baseView.valuederived.value 是否会访问同一字段;答案由 name binding 与选中的 class subobject 决定,不由内存中哪个字段更近决定。

3.1 The Binding of a Data Member

class member declaration 同时参与 type name、member name 和 scope rules。现代 compiler 必须遵循完整语言规则,但原书提醒我们:早期实现采用逐行或多阶段处理时,嵌套 typedef、inline member body 与后出现声明可能暴露错误绑定。

using Length = int;
 
class Point3d {
public:
    void set(Length value) { coordinate_ = value; }
 
private:
    using Coordinate = double;
    Coordinate coordinate_ = 0.0;
};

set 的 parameter type 明确绑定到外部 Lengthcoordinate_ 绑定到 class 内 Coordinate。可读代码应避免用同名 nested type 依赖细微 lookup timing。更重要的是,name binding 只回答“选中哪个 declaration”;它还没有回答 member 放在 object 的哪个 offset。

3.2 Data Member Layout

nonstatic member 占每个 object 的 storage;static member 不占 instance bytes。layout 还要满足每个 field alignment,因而可能在 members 之间插入 padding,并把 object size 向整体 alignment 取整。base tail padding 能否复用、empty base optimization、[[no_unique_address]] 等细节依赖语言版本与 ABI,不能用一条“字段顺序相加”概括。

struct Packet {
    char tag;
    std::uint32_t length;
    char flags;
};
 
static_assert(sizeof(Packet) >=
              sizeof(char) + sizeof(std::uint32_t) + sizeof(char));

这条断言只表达下界。常见 ABI 会在 tag 后插 padding 以对齐 length,并在末尾补齐 array stride。交换 member declaration 可能减小 size,却可能破坏 ABI、serialization 或 cache access pattern;需要用 alignof、member addresses 和 target ABI 文档验证。

access specifier 主要控制访问,不是可靠的物理分区标签。标准对 member address order 的保证随语言版本演进,跨版本/ABI 的持久化格式仍不应依赖原生 class layout。稳定 wire format 应逐字段编码。

3.3 Access of a Data Member

Type::count 通常 lower 为对一个具有 linkage/storage duration 的实体访问。写成 object.count 也不意味着 compiler 从 object 内取值;object expression 只参与语言语法和可能的求值规则,static storage 仍独立存在。

对普通 nonvirtual base/member,this->field 常能 lower 为 *(this + constant_offset)。public/protected/private 不改变这条运行时路径。inline accessor 在 definition 可见时通常也能回到同样 load/store,因此“封装必然比 struct 慢”没有普遍依据。

3.4 Inheritance and the Data Member

Inheritance without Polymorphism

普通 single inheritance 下,derived object 含完整 base subobject。base member access 往往仍是 compile-time offset,derived-to-base pointer conversion 也常是 fixed adjustment。注意 base 自身的 tail padding 与 derived member 安置由 ABI 决定;不能简单假设 derived field 一定紧跟 base 最后一个显式 field。

Adding Polymorphism

加入 virtual function 常让 class 获得 implementation vptr,并提高 size/alignment。但 data member 本身没有变成“虚数据”;base/derived ordinary fields 仍通常按 fixed layout 访问。动态成本主要在 virtual call 和可能的 pointer adjustment,而不是每次读 x 都查 vtable。

Multiple Inheritance

multiple inheritance 在同一 most-derived object 内放多个 base subobjects。转换到非首要 base view 可能调整 address;此后该 base 的 own data 仍可用相对其 base pointer 的 fixed offset 访问。错误地把原始 derived address 当成任意 base address,会读到错误 segment。

Virtual Inheritance

virtual base 的位置需要兼容不同 most-derived layouts,常不能编码成当前 class 的单一固定 offset。ABI 可通过 vtable entry、virtual-base table 或其他 metadata 得到 adjustment。访问 shared base data 因而可能比普通 base 多一次 load/add,也更限制 layout assumptions。

3.5 Object Member Efficiency

效率分析要拆成 load count、dependency chain、cache locality 与 optimizer visibility。普通 nonstatic field、inline getter 和 C struct field 在优化后可能生成相同 instruction;static data 类似 global access;nonvirtual base field 常只是 constant offset。virtual base access 才常多出 metadata load 和 adjustment。

微基准必须阻止 dead-code elimination、固定相同 data volume,并分别测 cold/warm cache。若 workload 被 memory latency 主导,一次额外 pointer chase 的影响可能远大于算术;若 compiler 已 hoist virtual-base offset 到 loop 外,单次源码访问数又不能直接代表每次迭代成本。

3.6 Pointer to Data Members

struct Record {
    int id;
    int count;
};
 
int Record::* selected = &Record::count;
Record record{7, 12};
Record* pointer = &record;
 
int a = record.*selected;
int b = pointer->*selected;

member pointer 不是 int*:形成 &Record::count 时没有任何 Record instance。常见 ABI 会编码 relative offset,并为 null member pointer 保留特殊表示;多重/虚继承转换可能需要 adjustment 或受到语言限制。不要序列化其 bytes,也不要假设零就是 null。

效率上,已知 ordinary member pointer 常是一次 add 后 load,optimizer 若看到常量还可折叠为直接 offset。runtime-selected member 保留动态 add;涉及 inheritance conversion 时可能有额外 adjustment。应与 switch、offset table 等实际替代方案在同一 workload 下比较。

第3章验证清单

  1. 先完成 member lookup/binding,再讨论 storage offset。
  2. 区分 nonstatic instance bytes、static storage 与 function code。
  3. 用 declaration、alignment 和 ABI 证据解释 padding,不凭字段和估算 sizeof
  4. 对 inheritance 分别画 base subobject、derived data 与 polymorphic metadata。
  5. multiple inheritance 先转换到正确 base view,再应用 base-relative offset。
  6. virtual inheritance 明确 shared base 的 runtime location 来源。
  7. 比较 accessor 与 direct field 时检查 optimized output,而不是只数源码层调用。
  8. 把 pointer-to-data-member 当 class-relative locator,不当 raw address 或稳定序列化值。

小结

  • data member binding 决定声明身份,layout 决定位置,machine access 才执行 address calculation
  • nonstatic data 进入每个 object,static data 独立存储,member function code 不随实例复制
  • padding、base layout 与 ABI metadata 共同决定 sizeof,不能靠显式字段字节简单相加
  • inheritance without polymorphism 的 ordinary data access 通常仍是 fixed offset
  • adding polymorphism 增加 virtual representation,但不会让所有数据访问自动虚化
  • multiple inheritance 主要要求正确 base pointer adjustment,virtual inheritance 还需定位共享 base
  • object member efficiency 应由优化后指令和实际 memory behavior 验证
  • pointer to data members 是 class-relative locator,应用到 object 后才产生 member lvalue

名词解释

本章出现的专业名词,用大白话再讲一遍。

binding of a data member
把成员名解析到特定 class declaration。
data member layout
为 base 和 nonstatic member 分配对象内位置。
padding
为 alignment 与 array stride 保留的表示字节。
static data member
位于对象外、由 class 共享一份的成员。
nonstatic data member
以 object/base address 定位的实例字段。
inheritance without polymorphism
不需要虚分派或共享虚基定位的继承表示。
polymorphic representation
支持 runtime virtual behavior 的对象元数据。
virtual inheritance
在 most-derived object 中共享一份 base 的继承。
object member efficiency
按真实访问路径比较对象成员成本。
pointer to data members
未绑定对象的 class-relative member locator。
member pointer application
用 .* 或 ->* 把 locator 应用到 object。

练习

  1. 问题 1:推导同名成员与布局。 Derived 同时继承 Base::value 并声明自己的 value;分别分析 derived.valuebaseRef.valuederived.Base::value 的 binding 与 address 来源。
  1. 问题 2:比较四种继承数据访问。 对普通单继承、加入 virtual function、多重继承的第二 base、virtual base 各写出 address calculation 和可能的额外 load。
  1. 问题 3:设计 pointer-to-data-member 实验。 runtime 在 idcount 之间选择 member pointer,应用到 object;再加入 base conversion,比较 direct access、member pointer 与 switch 的优化结果。

讨论

评论区加载中…