news 2026/8/6 5:39:16

class { ... }不是010101的數據盒,人寫的(叫cpp源碼)到編譯器編譯的(非可控,由微軟決定編譯)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
class { ... }不是010101的數據盒,人寫的(叫cpp源碼)到編譯器編譯的(非可控,由微軟決定編譯)

class { ... }想成一個實體盒子,認為大括號裡寫的類型、變數和函數,最後都要一起塞進每個物件內部的 0/1 裡。--這是錯誤的

類別宣告是編譯器閱讀的一份規則文件,不是一整塊會原樣存在於執行期記憶體中的資料。Epic 也把 class 描述成建立 Object/Actor 的模板;類別標頭裡是「宣告」函數與屬性,不是說每個物件都攜帶一份函數。(Epic Games Developers)

看這個:

struct FVectorLike { using Scalar = float; float X; float Y; float Z; float LengthSquared() const { return X * X + Y * Y + Z * Z; } };

你在源碼中看到它們都寫在同一對{}裡,但編譯後會被分流到完全不同的地方。

FVectorLike 的宣告 ├─ using Scalar = float │ └─ 只給編譯器建立一個類型別名 │ └─ FVectorLike 物件裡不佔空間 │ ├─ float X, Y, Z │ └─ 決定每個物件的資料布局 │ └─ 每個 FVectorLike 物件真正擁有三個 float │ └─ LengthSquared() └─ 編譯成一份機器指令 └─ 所有 FVectorLike 物件共同使用

一個物件真正存了什麼

例如:

FVectorLike A; FVectorLike B;

執行期可以近似理解成:

A 的記憶體 ├─ X:4 bytes ├─ Y:4 bytes └─ Z:4 bytes B 的記憶體 ├─ X:4 bytes ├─ Y:4 bytes └─ Z:4 bytes

在典型 Windows C++ 實作中,它們通常各自是 12 bytes;實際布局仍由型別、對齊及編譯器 ABI 決定。C++ 所謂 object layout,主要就是描述資料成員如何排列在物件記憶體裡。(Microsoft Learn)

它們不會變成:

A ├─ X ├─ Y ├─ Z ├─ Scalar 類型資訊 └─ LengthSquared 函數的全部機器碼

函數沒有複製進 A,也沒有複製進 B。

!!!那A.LengthSquared()怎麼知道操作 A?

這是關鍵。

你寫:

float Result = A.LengthSquared();

概念上,編譯器可以把它理解成:

float Result = FVectorLike_LengthSquared(&A);

也就是函數只有一份,但呼叫時偷偷多傳入

this = &A;

因此原來的:

return X * X + Y * Y + Z * Z;

概念上等於:

return this->X * this->X + this->Y * this->Y + this->Z * this->Z;

C++ 的非靜態成員函數具有隱含的this指標,指向目前正在被操作的物件。(Microsoft Learn)

所以:

A.LengthSquared(); B.LengthSquared();

使用的是同一份函數機器碼,區別只在傳進去的物件地址:

呼叫 A.LengthSquared() └─ this = A 的地址 呼叫 B.LengthSquared() └─ this = B 的地址

這就是「資料屬於每個物件,行為由所有物件共用」。

「類別裡可以存類型」也不是把類型塞進物件

例如:

struct FContainer { using ValueType = float; struct FIterator { int Index; }; ValueType Value; };

這裡:

FContainer ├─ ValueType │ └─ 一個作用域內的類型名稱 │ ├─ FIterator │ └─ 另一個類型的宣告 │ └─ Value └─ 真正的資料成員

你可以寫:

FContainer::ValueType Number; FContainer::FIterator Iterator;

FContainer::在這裡更像一個名稱作用域

FContainer └─ 裡面有哪些名稱 ├─ ValueType ├─ FIterator └─ Value

巢狀類型本身不會增加FContainer物件大小。

只有你真的寫:

struct FContainer { FIterator Iterator; };

這時Iterator才是物件資料,才會佔記憶體。

因此要區分:

struct FIterator {}; // 宣告一種類型 FIterator Iterator; // 放置一個該類型的物件

第一行只是讓編譯器知道「有這種形狀」。

第二行才是真的要求

按照FIterator的布局,給每個FContainer實例留出一塊記憶體。

最底層的 0/1 到底怎麼區分資料、類型和函數

0 和 1 本身沒有「這是 float」「這是函數」的天然標籤。

同一串 bits:

01000000 01001001 00001111 11011011

只有在不同解讀規則下,才可能被理解成:

└─ 當 float 解讀 └─ 一個浮點數 └─ 當 int32 解讀 └─ 一個整數 └─ 當機器指令的一部分解讀 └─ 某些 CPU opcode / operand

意義來自「誰在解讀」以及「按什麼規則解讀」。

完整鏈路是:

C++ 源碼文字 └─ 編譯器解析 ├─ 類型宣告 │ └─ 用於檢查尺寸、成員偏移、呼叫是否合法 │ ├─ 資料成員 │ └─ 形成物件的記憶體布局 │ └─ 函數 └─ 編譯成 CPU 機器指令 可執行檔 ├─ Code / .text │ └─ 函數的機器指令 │ ├─ Data │ └─ 全域與靜態資料 │ └─ 編譯器與連結器資訊 └─ 符號、重定位、除錯資訊等 執行時 ├─ CPU 指令指標指向 Code │ └─ CPU 把那些 bytes 當指令執行 │ └─ Load / Store 指令指向物件記憶體 └─ CPU 把那些 bytes 當資料讀寫

不是 bits 自己聲稱「我是函數」。

而是:

CPU 從指令指標位置取 bytes └─ 按指令集解碼 └─ 它們成為函數指令 CPU 執行讀取 float 的指令 └─ 從某地址取 4 bytes └─ 按浮點格式使用

!!!函數如果是虛函數,則以base類裡的所有Virtual函數給這個類湊成"表"

例如:

struct Base { virtual void F(); virtual void G(); }; struct ChildA : Base { void F() override; }; struct ChildB : Base { void G() override; };

概念上的布局是:

Base 的表 ├─ slot 0 → Base::F └─ slot 1 → Base::G ChildA 的表 ├─ slot 0 → ChildA::F └─ slot 1 → Base::G ChildB 的表 ├─ slot 0 → Base::F └─ slot 1 → ChildB::G

不是:

一張總表 ├─ Base::F ├─ ChildA::F ├─ ChildB::F ├─ Base::G ├─ ChildA::G └─ ChildB::G

而是每個類型都有一套固定槽位,槽位代表虛函數接口,槽位裡放的是:

對這個具體類型而言,該虛函數最終應該調用哪個實現。

Microsoft 的資料也把 vtable 描述為函數指標構成的表;虛呼叫會從固定 offset 的槽位讀出函數地址再調用。(Microsoft Learn)

所以class { ... }究竟是什麼

不要再把它理解成「一個裡面裝了資料和函數的盒子」。

更準確是:

class / struct ├─ 一個名稱作用域 │ ├─ 可放類型名稱 │ ├─ 可放函數名稱 │ └─ 可放變數名稱 │ ├─ 一份物件布局規則 │ └─ 非 static 資料成員決定物件裡有哪些 bytes │ ├─ 一組可作用於該物件的函數 │ └─ 通過 this 指標找到具體物件 │ └─ 一套存取與型別檢查規則 ├─ public ├─ protected └─ private

它不是單一物理實體,而是 C++ 把幾種相關規則收束到同一個名字下。

最終可以壓成一句:

類別大括號裡混合的是「宣告」,不是同一種執行期資料;編譯器會把類型規則留在編譯期,把資料成員變成物件布局,把函數變成共用機器碼,再用this把函數接到具體物件。

它們根本沒有作為同一坨 0/1 被塞進物件;只是在源碼層被放進同一個語義作用域。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/6 5:38:28

储能系统接地电阻柜选型与安全防护技术解析

1. 储能系统中的安全挑战与接地需求在新能源发电占比不断提升的今天,储能系统已成为电网稳定运行的关键基础设施。我参与过多个大型储能电站的调试工作,亲眼目睹过因接地不良引发的恶性事故。去年某30MW/60MWh的磷酸铁锂储能项目,就曾因雷击导…

作者头像 李华
网站建设 2026/8/6 5:35:42

权限维持技术深度解析:攻防实战中的持久化与隐蔽之道

1. 项目概述:一次关于“权限维持”的深度复盘在安全攻防的实战对抗中,拿到一个系统的访问权限,往往只是万里长征的第一步。真正考验技术深度和持久性的,是如何在目标环境中“站稳脚跟”,即使在被发现、被清理后&#x…

作者头像 李华
网站建设 2026/8/6 5:30:51

66G、34G、21G 怎么选|MiniMax H3 权重完整对比

MiniMax H3 本地部署到底选哪个权重?一张表看懂 别再盲目下载了,看完这篇省下几百 GB 硬盘 💾 🤔 同一个模型,为什么有这么多版本? MiniMax H3 发布后,开源社区放出了多个权重变体。很多朋友兴冲冲去下载,一看文件列表瞬间懵了——bf16、int8、fp8、pruned、ConvRot…

作者头像 李华
网站建设 2026/8/6 5:29:45

Unity网络编程核心八问:从Socket到协议栈的深度解析与实战指南

1. 项目概述:为什么Unity开发者必须啃下网络编程这块硬骨头?如果你是一名Unity开发者,并且你的职业规划里包含了“游戏客户端主程”、“技术专家”或者“独立制作人”,那么网络编程绝对是你绕不开的一道坎。这不仅仅是面试官喜欢问…

作者头像 李华
网站建设 2026/8/6 5:28:11

PixVerse Canvas:基于Canvas的素材创作与分享平台全解析

这次我们来看一个名为“PixVerse Canvas”的项目。从名称和网络热词来看,它很可能是一个与Canvas(画布)技术相关的内容创作或素材分享平台或工具。Canvas作为Web前端强大的图形绘制API,广泛应用于数据可视化、图像处理、游戏和创意…

作者头像 李华