Golang 内存模型详解(一)

开始之前

首先,这是一篇菜B写的文章,可能会有理解错误的地方,发现错误请斧正,谢谢。

为了治疗我的懒癌早期,我一次就不写得太多了,这个系列想写很久了,每次都是开了个头就没有再写。这次争取把写完,弄成一个系列。

此 nil 不等彼 nil

先声明,这个标题有标题党的嫌疑。

Go 的类型系统是比较奇葩的,nil 的含义跟其它语言有些差别,这里举个例子(可以直接进入 http://play.golang.org/p/ezFhXX0dnB 运行查看结果):

代码如下:

package main
import "fmt"
type A struct {
}
func main() {
    var a *A = nil
    var ai interface{} = a
    var ei interface{} = nil
    fmt.Printf("ai == nil: %v\n", ai == nil)
    fmt.Printf("ai == ei: %v\n", ai == ei)
    fmt.Printf("ei == a: %v\n", a == ei)
    fmt.Printf("ei == nil: %v\n", ei == nil)
}
// -> 输出
// ai == nil: false
// ai == ei: false
// ei == a: false
// ei == nil: true

这里 ai != nil,对于没有用过 Go 的人来说比较费解,对我来说,这个算得上一门语言设计有歧义的地方(Golang FAQ 有对于此问题的描述,可以参考一下:http://golang.org/doc/faq#nil_error)。

简单的说就是 nil 代表 “zero value”(空值),对于不同类型,它具体所代表的值不同。比如上面的 a 为“*A 类型的空值”,而 ai 为“interface{} 类型的空值”。造成理解失误的最大问题在于,struct pointer 到 interface 有隐式转换(var ai interface{] = a,这里有个隐式转换),至于为什么对于 Go 这种在其它转换方面要求严格,而对于 interface 要除外呢,for convenience 吧,呵呵……

碰到了这个坑,我就开始好奇了,Go 的类型系统到底是什么样的?

Go 内存模型 - interface

概述

为了读懂下面的内容,你需要:

了解 C、Go 语言

Go 1.3 源代码 (https://go.googlecode.com/archive/go1.3.zip)

PS: 由于 Go 用到了 Plan9 C 这个小众的C编译器的扩展,比如在函数签名中使用 · 字符以区分 package/function(比如runtime·panic),这对理解不会产生什么影响。

PSS: 对于 Go runtime,可以参考src/pkg/reflect(reflect包)中的的代码,对类型系统的实现的理解有帮助。

Go 语言的类型定义可以在 src/pkg/runtime/ 目录下找到,主要由以下几个文件构成:

1.runtime.h
2.type.h

对于 interface 类型,主要看下面几个结构体定义:

1.InterfaceType
2.Itab
3.Iface
4.Eface

它们的C语言定义如下 (可以在 runtime.h 中找到):

InterfaceType:

代表了总的 interface 类型,其中:

1.Type: 类型描述,所有的类型都有这个类型描述(比如 array, map, slice)
2.mhdr 以及 m: interface 接口方法列表

代码如下:

struct InterfaceType
{
    Type;
    Slice mhdr;
    IMethod m[];
};

Itab:

类似于虚函数表,该表不会被GC回收,其中:

1.inter: 指向具体的 interface 类型
2.type: 具体实现类型, 也即 receiver type
3.link: 指向下一个函数表,因为 interface 可以 embed 多个 interface,因此实现为一个链表形式
4.bad: <略>
5.unsued: <略>
6.fun: 函数列表,每个元素是一个指向具体函数实现的指针

代码如下:

struct  Itab
{
    InterfaceType*  inter;
    Type*   type;
    Itab*   link;
    int32   bad;
    int32   unused;
    void    (*fun[])(void);
};

Iface:

该类型为一般的 interface 类型所对应的数据结构,其中:

1.tab: 参见 Itab 的说明,尤其是 Itab::link
2.data: 指向具体数据(比如指向struct,当然,如果一个数据不超过一个字长,那么这个data就可以直接存放,不需要指针再做以及跳转)

代码如下:

struct Iface
{
    Itab*   tab;
    void*   data;
};

Eface:

该类型为 interface{} (empty interface) 所对应的数据结构,其中:

1.type: 具体实现类型, 也即 receiver type
2.data: 同 Iface

代码如下:

struct Eface
{
    Type*   type;
    void*   data;
};

他们的依赖关系如下图所示:

先到这里,下一篇将会举例子说明给一个 interface{} 类型的变量赋值后,其具体的内存结构是怎么样的。

打了几个小时,真费时间,争取这个系列不坑 (逃

(0)

相关推荐

  • GO语言标准错误处理机制error用法实例

    本文实例讲述了GO语言标准错误处理机制error用法.分享给大家供大家参考.具体分析如下: 在 Golang 中,错误处理机制一般是函数返回时使用的,是对外的接口,而异常处理机制 panic-recover 一般用在函数内部. error 类型介绍 error 类型实际上是抽象了 Error() 方法的 error 接口,Golang 使用该接口进行标准的错误处理. 复制代码 代码如下: type error interface {  Error() string } 一般情况下,如果函数需要返

  • Go语言中的Array、Slice、Map和Set使用详解

    Array(数组) 内部机制 在 Go 语言中数组是固定长度的数据类型,它包含相同类型的连续的元素,这些元素可以是内建类型,像数字和字符串,也可以是结构类型,元素可以通过唯一的索引值访问,从 0 开始. 数组是很有价值的数据结构,因为它的内存分配是连续的,内存连续意味着可是让它在 CPU 缓存中待更久,所以迭代数组和移动元素都会非常迅速. 数组声明和初始化 通过指定数据类型和元素个数(数组长度)来声明数组. 复制代码 代码如下: // 声明一个长度为5的整数数组 var array [5]int

  • Go语言的GOPATH与工作目录详解

    GOPATH设置 go 命令依赖一个重要的环境变量:$GOPATH1 (注:这个不是Go安装目录.下面以笔者的工作目录为说明,请替换自己机器上的工作目录.) 在类似 Unix 环境大概这样设置: 复制代码 代码如下: export GOPATH=/home/apple/mygo 为了方便,应该把新建以上文件夹,并且把以上一行加入到 .bashrc 或者 .zshrc 或者自己的 sh 的配置文件中. Windows 设置如下,新建一个环境变量名称叫做GOPATH: 复制代码 代码如下: GOPA

  • Go语言运行环境安装详细教程

    Go的三种安装方式 Go有多种安装方式,你可以选择自己喜欢的.这里我们介绍三种最常见的安装方式: 1.Go源码安装:这是一种标准的软件安装方式.对于经常使用Unix类系统的用户,尤其对于开发者来说,从源码安装是最方便而熟悉的. 2.Go标准包安装:Go提供了方便的安装包,支持Windows.Linux.Mac等系统.这种方式适合初学者,可根据自己的系统位数下载好相应的安装包,一路next就可以轻松安装了. 3.第三方工具安装:目前有很多方便的第三方软件包工具,例如Ubuntu的apt-get.M

  • Go语言interface详解

    interface Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计所折服. 什么是interface 简单的说,interface是一组method的组合,我们通过interface来定义对象的一组行为. 我们前面一章最后一个例子中Student和Employee都能SayHi,虽然他们的内部实现不一样,但是那不重要,重要的是他们都能say hi 让我们来继续做更多的扩展,Student和Employe

  • Go语言共享内存读写实例分析

    本文实例分析了Go语言共享内存读写的方法.分享给大家供大家参考.具体分析如下: 前面分析了Go语言指针运算和内嵌C代码的方法,做了一个Go语言共享内存读写的实验. 先大概说下什么是共享内存.我们知道不同进程见的内存是互相独立的,没办法直接互相操作对方内的数据,而共享内存则是靠操作系统提供的内存映射机制,让不同进程的一块地址空间映射到同一个虚拟内存区域上,使不同的进程可以操作到一块共用的内存块.共享内存是效率最高的进程间通讯机制,因为数据不需要在内核和程序之间复制. 共享内存用到的是系统提供的mm

  • Go语言命令行操作命令详细介绍

    Go 命令 Go语言自带有一套完整的命令操作工具,你可以通过在命令行中执行go来查看它们: 图1.3 Go命令显示详细的信息 这些命令对于我们平时编写的代码非常有用,接下来就让我们了解一些常用的命令. go build 这个命令主要用于测试编译.在包的编译过程中,若有必要,会同时编译与之相关联的包. 1.如果是普通包,就像我们在1.2节中编写的mymath包那样,当你执行go build之后,它不会产生任何文件.如果你需要在$GOPATH/pkg下生成相应的文件,那就得执行go install了

  • Go语言中的内存布局详解

    一.go语言内存布局 想象一下,你有一个如下的结构体. 复制代码 代码如下: type MyData struct {         aByte   byte         aShort  int16         anInt32 int32         aSlice  []byte } 那么这个结构体究竟是什么呢? 从根本上说,它描述了如何在内存中布局数据. 这是什么意思?编译器又是如何展现出来呢? 我们来看一下. 首先让我们使用反射来检查结构中的字段. 二.反射之上 下面是一些使用

  • Go语言实现简单的一个静态WEB服务器

    学习Go语言的一些感受,不一定准确. 假如发生战争,JAVA一般都是充当航母战斗群的角色. 一旦出动,就是护卫舰.巡洋舰.航母舰载机.预警机.电子战飞机.潜艇等等 浩浩荡荡,杀将过去. (JVM,数十个JAR包,Tomcat中间件,SSH框架,各种配置文件...天生就是重量级的,专为大规模作战) 而GO语言更像F35战斗轰炸机 单枪匹马,悄无声息,投下炸弹然后走人. 专属轰炸机,空战也会一点点. 实在搞不定,就叫它大哥F22. (GO是编译型语言,不需要依赖,不需要虚拟机,可以调用C代码并且它足

  • GO语言并发编程之互斥锁、读写锁详解

    在本节,我们对Go语言所提供的与锁有关的API进行说明.这包括了互斥锁和读写锁.我们在第6章描述过互斥锁,但却没有提到过读写锁.这两种锁对于传统的并发程序来说都是非常常用和重要的. 一.互斥锁 互斥锁是传统的并发程序对共享资源进行访问控制的主要手段.它由标准库代码包sync中的Mutex结构体类型代表.sync.Mutex类型(确切地说,是*sync.Mutex类型)只有两个公开方法--Lock和Unlock.顾名思义,前者被用于锁定当前的互斥量,而后者则被用来对当前的互斥量进行解锁. 类型sy

随机推荐