深度探索 Go 对象模型(一)

简介: 深度探索 Go 对象模型

了解一门语言的高级特性,仅仅从浮于表面,是无法把握住语言的精髓的。学习过 C++ 的高阶开发者,一定读过神书《Inside The C++ Object Model》,本文的目标是一样的:通过对象模型,掌握 Go 语言的底层机制,从更深层次解释语言特性。

编译与执行

众所周知,Go 源码并不能直接运行,所有代码必须一行行,通过“编译”——“汇编”——“链接” 阶段 转化为低级的机器语言指令,即可执行程序。

“汇编”和“链接”阶段各种语言并无区别,所以一般通过“编译”和“执行”阶段来支持各种语言特性。对于 Go 语言,执行过程并无法直接修改执行指令,因此所有语言特性都是“编译”相关的。理解这一点很重要,因为下面依赖“编译”的产物 汇编代码 来解读对象模型。

什么是对象模型?

何为 Go 对象模型? Go 对象模型可以概括为以下两部分:

  1. 支持面向对象程序设计的部分
  • 封装
  • 继承
  • 多态
  1. 各种特性的底层实现机制
  • 反射

下面分别从 struct 和 interface 来解释模型如何支持以上两部分。

Struct 语意学

面向对象编程,把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数,前者为成员变量,后者为成员函数。所以研究对象需要分别从成员变量和成员函数入手。

成员变量

以下有三段程序:

// First: global varible
var (
   X,Y,Z float32
)
// Second: simple type
type point3d struct {
  X, Y, Z float32
}
// Third: inherit type
type point struct {
  X float32
}
type point2d struct {
  point
  Y float32
}
type point3d struct {
  point2d
  Z float32
}

从风格来看,三段程序截然不同。有许多令人信服的讨论告诉我们,为什么“数据封装”(Second & Third)要比使用“全局变量”好。但,从程序员的角度看,会有几个疑问:

  1. “数据封装” 之后,内存成本增加了多少?
  2. “数据封装” 之后,在执行过程中,变量的存储效率是否降低了?
内存布局

先看内存变化。了解内存变化最好的办法就是通过代码打印对象的内存大小,先看全局变量大小

var (
  X, Y, Z float32
)
func main() {
  fmt.Printf("X size:%v, Y size:%v, Z size:%v\n", unsafe.Sizeof(X), unsafe.Sizeof(Y), unsafe.Sizeof(Z))
  fmt.Printf("X addr:%v, Y addr:%v, Z addr:%v\n", &X, &Y, &Z)
}

执行程序输出为:

$ go run variable.go
X size:4, Y size:4, Z size:4
X addr:0x118ee88, Y addr:0x118ee8c, Z addr:0x118ee90

可以看到,X、Y、Z三个字段大小均为4字节,且三个字段内存地址顺序排列。

再看第二段代码的输出

func TestLayout(t *testing.T) {
  p := point3d{X: 1, Y: 2, Z: 3}
  fmt.Printf("point3d size:%v, align:%v\n", unsafe.Sizeof(p), unsafe.Alignof(p))
  typ := reflect.TypeOf(p)
  fmt.Printf("Struct:%v is %d bytes long\n", typ.Name(), typ.Size())
  fmt.Printf("X at offset %v, size=%d\n", unsafe.Offsetof(p.X), unsafe.Sizeof(p.X))
  fmt.Printf("Y at offset %v, size=%d\n", unsafe.Offsetof(p.Y), unsafe.Sizeof(p.Y))
  fmt.Printf("Z at offset %v, size=%d\n", unsafe.Offsetof(p.Z), unsafe.Sizeof(p.Z))
}

执行程序输出为:

$ go test -v -run TestLayout
=== RUN   TestLayout
point3d size:12, align:4
Struct:point3d is 12 bytes long
X at offset 0, size=4
Y at offset 4, size=4
Z at offset 8, size=4

可以看到,X、Y、Z三个字段大小一样为4字节,内存排列也与上一个版本一样。

继续,第三段代码

func TestLayout(t *testing.T) {
  p := point3d{point2d: point2d{point: point{X: 1}, Y: 2}, Z: 3}
  fmt.Printf("point3d size:%v, align:%v\n", unsafe.Sizeof(p), unsafe.Alignof(p))
  typ := reflect.TypeOf(p)
  fmt.Printf("Struct:%v is %d bytes long\n", typ.Name(), typ.Size())
  fmt.Printf("X at offset %v, size=%d\n", unsafe.Offsetof(p.X), unsafe.Sizeof(p.X))
  fmt.Printf("Y at offset %v, size=%d\n", unsafe.Offsetof(p.Y), unsafe.Sizeof(p.Y))
  fmt.Printf("Z at offset %v, size=%d\n", unsafe.Offsetof(p.Z), unsafe.Sizeof(p.Z))
}

执行程序输出为:

$ go test -v -run TestLayout
=== RUN   TestLayout
point3d size:12, align:4
Struct:point3d is 12 bytes long
X at offset 0, size=4
Y at offset 4, size=4
Z at offset 8, size=4

可以看到,X、Y、Z三个字段大小一样为4字节,内存排列也与之前两个版本一样。

综上所述,我们可以看到,无论是否封装,还是多深的继承层次,对成员变量的内存布局都并无影响,均按照字段定义的顺序排列(不考虑内存对齐的情况)。即内存布局类似如下:


目录
相关文章
|
3月前
|
Go 调度 开发者
CSP模型与Goroutine调度的协同作用:构建高效并发的Go语言世界
【2月更文挑战第17天】在Go语言的并发编程中,CSP模型与Goroutine调度机制相互协同,共同构建了高效并发的运行环境。CSP模型通过通道(channel)实现了进程间的通信与同步,而Goroutine调度机制则确保了并发任务的合理调度与执行。本文将深入探讨CSP模型与Goroutine调度的协同作用,分析它们如何共同促进Go语言并发性能的提升。
|
3月前
|
Go 开发者
Go语言并发模型概览:CSP模型解析
【2月更文挑战第17天】Go语言以其强大的并发处理能力在编程领域崭露头角。其中,CSP(Communicating Sequential Processes)模型作为Go语言并发模型的核心之一,在并发编程中发挥着至关重要的作用。本文将深入解析CSP模型的基本原理及其在Go语言中的应用,帮助读者更好地理解Go语言的并发编程特性。
|
3月前
|
Go 开发者
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
掌握Go语言:Go语言结构体,精准封装数据,高效管理实体对象(22)
|
26天前
|
缓存 编译器 Go
开发与运维线程问题之Go语言的goroutine基于线程模型实现如何解决
开发与运维线程问题之Go语言的goroutine基于线程模型实现如何解决
37 3
|
2月前
|
Go 开发者
探索Go语言的并发编程模型
通过实例详细介绍了Go语言中的并发编程模型,包括goroutine、channel的基本使用和最佳实践。深入剖析如何利用Go的并发特性提高程序性能和效率,适用于初学者和有一定经验的开发者。
|
3月前
|
安全 Go 开发者
Golang深入浅出之-Go语言中的CSP模型:深入理解并发哲学
【5月更文挑战第2天】Go语言的并发编程基于CSP模型,强调通过通信共享内存。核心概念是goroutines(轻量级线程)和channels(用于goroutines间安全数据传输)。常见问题包括数据竞争、死锁和goroutine管理。避免策略包括使用同步原语、复用channel和控制并发。示例展示了如何使用channel和`sync.WaitGroup`避免死锁。理解并发原则和正确应用CSP模型是编写高效安全并发程序的关键。
73 4
|
3月前
|
安全 Go 开发者
Golang深入浅出之-Go语言中的CSP模型:深入理解并发哲学
【5月更文挑战第1天】Go语言基于CSP理论,借助goroutines和channels实现独特的并发模型。Goroutine是轻量级线程,通过`go`关键字启动,而channels提供安全的通信机制。文章讨论了数据竞争、死锁和goroutine泄漏等问题及其避免方法,并提供了一个生产者消费者模型的代码示例。理解CSP和妥善处理并发问题对于编写高效、可靠的Go程序至关重要。
65 2
|
3月前
|
数据可视化
R语言多元(多变量)GARCH :GO-GARCH、BEKK、DCC-GARCH和CCC-GARCH模型和可视化
R语言多元(多变量)GARCH :GO-GARCH、BEKK、DCC-GARCH和CCC-GARCH模型和可视化
|
3月前
|
物联网 大数据 Go
深入探索Go语言的并发编程模型
Go语言以其简洁、高效和强大的并发编程能力而备受瞩目。本文旨在揭示Go语言并发编程模型的独特之处,通过对其goroutine和channel机制的深入剖析,展示Go语言在并发编程领域的优势。文章不仅介绍了Go语言并发编程的基本概念,还通过实例展示了其在实际应用中的强大威力。
|
3月前
|
消息中间件 存储 Java
Java与Go的生产者消费者模型比较
【4月更文挑战第20天】
35 1