问题1:什么是包装方法?

下面咱们来验证下包装方法的存在:

首先,定义一个Point类型,表示一维坐标系内的一个点,并且按照Go语言的风格为其实现了一个Get方法和一个Set方法。

package gom

type Point struct {
x float64
}

func (p Point) X() float64 {
return p.x
}

func (p *Point) SetX(x float64) {
p.x = x
}

然后,采用只编译不链接的方式来得到OBJ文件,再对编译得到的OBJ文件进行反编译分析。编译命令如下:

$ go tool compile -trimpath="`pwd`=>" -l -p gom point.go

上述命令禁用了内联优化,编译完成后会在当前工作目录生成一个point.o文件,这就是我们想要的OBJ文件。

接下来,通过go tool nm可以查看该文件中实现了哪些函数,nm会输出OBJ文件中定义或使用到的符号信息,通过grep命令过滤代码段符号对应的T标识,即可查看文件中实现的函数:

$ go tool nm point.o | grep T
1562 T gom.(*Point).SetX
1899 T gom.(*Point).X
1555 T gom.Point.X

可以看到point.o中一共实现了3个方法,它们都定义在Point类型所在的gom包中:

第一个是Point的SetX方法,它的接收者类型是*Point,第三个是Point的X方法,它的接收者类型是Point,这些都与源代码一致。

比较奇怪的是第二个方法,这是一个接收者类型为*Point的X方法,源代码中并没有这个方法,它是怎么来的呢?只能是编译器生成的。

编译器会为接收者为值类型的方法生成接收者为指针类型的方法,也就是所谓的“包装方法”。

那么编译器为什么要生成它呢?


问题2:为什么要生成包装方法?

如果是为了支持通过指针直接调用值接收者方法,那么直接在调用端进行指针解引用就可以了,总不至于为此生成包装方法吧?

为了验证这个问题,笔者又写了个函数用来反编译:

实验:包装方法是否为了支持通过指针直接调用值接收者方法

func PointX(p *Point) float64 {
return p.X()
}

大致思路就是:通过指针来调用值接收者方法,再通过反编译看一下实际调用的是不是包装方法。反编译得到的汇编代码如下:

$ go tool objdump -S -s '^gom.PointX$' point.o
TEXT gom.PointX(SB) gofile..point.go
func PointX(p *Point) float64 {
0x1a17 65488b0c2528000000 MOVQ GS:0x28, CX
0x1a20 488b8900000000 MOVQ 0(CX), CX [3:7]R_TLS_LE
0x1a27 483b6110 CMPQ 0x10(CX), SP
0x1a2b 7637 JBE 0x1a64
0x1a2d 4883ec18 SUBQ $0x18, SP
0x1a31 48896c2410 MOVQ BP, 0x10(SP)
0x1a36 488d6c2410 LEAQ 0x10(SP), BP
return p.X()
0x1a3b 488b442420 MOVQ 0x20(SP), AX
0x1a40 f20f1000 MOVSD_XMM 0(AX), X0
0x1a44 f20f110424 MOVSD_XMM X0, 0(SP)
0x1a49 e800000000 CALL 0x1a4e [1:5]R_CALL:gom.Point.X
0x1a4e f20f10442408 MOVSD_XMM 0x8(SP), X0
0x1a54 f20f11442428 MOVSD_XMM X0, 0x28(SP)
0x1a5a 488b6c2410 MOVQ 0x10(SP), BP
0x1a5f 4883c418 ADDQ $0x18, SP
0x1a63 c3 RET
func PointX(p *Point) float64 {
0x1a64 e800000000 CALL 0x1a69 [1:5]R_CALL:runtime.morestack_noctxt
0x1a69 ebac JMP gom.PointX(SB)

可以看到p.X()实际上会在调用端对指针解引用,然后调用值接收者方法(本质上就是编译器提供的语法糖),并没有调用编译器生成的包装方法。那这个包装方法究竟有什么用途呢?


真正的原因

之前我们已经介绍过接口的数据结构iface,它包含一个itab指针和一个data指针,data指针存储的就是数据的地址。

type iface struct {
tab *itab
data unsafe.Pointer
}

对于接口来讲,在调用指针接收者方法时,传递地址是非常方便的,也不用关心数据的具体类型,地址的大小总是一致的。

假如通过接口调用值接收者方法,就需要通过接口中的data指针把数据的值拷贝到栈上,由于编译阶段不能确定接口背后的具体类型,所以编译器不能生成相关的指令来完成拷贝,所以说,接口是不能直接使用值接收者方法的,这就是编译器生成包装方法的根本原因。


那么,就没有什么办法可以让接口间接使用值接收者方法吗?

还记得介绍defer相关内容时讲到的runtime.reflectcall函数吗?它能够在运行阶段动态的拷贝参数并完成函数调用。

如果基于reflectcall的话,能不能实现通过接口调用值接收者方法呢?肯定是可以实现的,接口的itab中有具体类型的元数据,确实能够应用reflectcall。但是有个明显的问题:性能太差。跟几条用于传参的MOV指令加一条普通的CALL指令相比,reflectcall的开销太大了,所以Go语言选择为值接收者方法生成包装方法。

但是如果反编译或者用nm命令来分析可执行文件的话,就会发现:

不只是这些包装方法,就连代码中的原始方法也不一定会存在于可执行文件中。

这是怎么回事呢?(未完待续~)

深度探索Go语言:包装方法的更多相关文章

  1. [转]深度探索C语言函数可变长参数

    转自:http://www.cnblogs.com/chinazhangjie/archive/2012/08/18/2645475.html 一.基础部分 1.1 什么是可变长参数 可变长参数:顾名 ...

  2. 深度解密Go语言之Slice

    目录 当我们在说 slice 时,到底在说什么 slice 的创建 直接声明 字面量 make 截取 slice 和数组的区别在哪 append 到底做了什么 为什么 nil slice 可以直接 a ...

  3. 深度探索C++对象模型

    深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...

  4. 读书笔记《深度探索c++对象模型》 概述

    <深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...

  5. Delphi深度探索-CodeSite应用指南

    Delphi深度探索-CodeSite应用指南 Delphi虽然为我们提供极其强大的调试功能,查找Bug仍然是一项艰巨的工作,通常我们写代码和调试代码的所消耗的时间是大致相同的,甚至有可能更多.为了减 ...

  6. 柔性数组-读《深度探索C++对象模型》有感 (转载)

    最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...

  7. 柔性数组-读《深度探索C++对象模型》有感

    最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...

  8. 探索equals()和hashCode()方法

    探索equals()和hashCode()方法 在根类Object中,实现了equals()和hashCode()这两个方法,默认: equals()是对两个对象的地址值进行的比较(即比较引用是否相同 ...

  9. Google资深工程师深度讲解Go语言完整教程

    资源获取链接点击这里 欢迎大家来到深度讲解Go语言的课堂.本课程将从基本语法讲起,逐渐深入,帮助同学深度理解Go语言面向接口,函数式编程,错误处理,测试,并行计算等元素,并带领大家实现一个分布式爬虫的 ...

  10. 侯捷STL学习(七)--深度探索vector&&array

    layout: post title: 侯捷STL学习(七) date: 2017-06-13 tag: 侯捷STL --- 第十六节 深度探索vector vector源码剖析 vector内存2倍 ...

随机推荐

  1. 安装docker及使用docker安装其他软件(手动挂载数据卷)

    中秋明月,豪门有,贫家也有,极慰人心 Linux安装docker 可以参考官方的安装文档 centos安装docker: https://docs.docker.com/engine/install/ ...

  2. SpringMVC--从理解SpringMVC执行流程到SSM框架整合

    前言 SpringMVC框架是SSM框架中继Spring另一个重要的框架,那么什么是SpringMVC,如何用SpringMVC来整合SSM框架呢?下面让我们详细的了解一下. 注:在学习SpringM ...

  3. Andrej Karpathy | 详解神经网络和反向传播(基于 micrograd)

    只要你懂 Python,大概记得高中学过的求导知识,看完这个视频你还不理解反向传播和神经网络核心要点的话,那我就吃鞋:D Andrej Karpathy,前特斯拉 AI 高级总监.曾设计并担任斯坦福深 ...

  4. 2.2 virtualenv 虚拟环境

    有的时候因为各种原因,在操作系统下,我们会安装很多版本的Python解释器.同样,我们也有可能因为各种原因,需要不同版本的模块,比如Django1.8,Django1.11.再加上pip工具管理器的版 ...

  5. logstash处理字段样例

  6. linux系统排查数据包常用命令

    1.查看当前系统中生效的所有参数 sysctl -a 2.统计处于TIME_WAIT状态的TCP连接数 netstat -ant|grep TIME_WAIT|wc -l 3.统计TCP连接数 net ...

  7. Intellij IDEA个人常用快捷键

    分享一下个人常用快捷键. 说明:字母排序规则遵循字母表(a->z) 快捷键 介绍 ctrl+b 快速打开当前光标处的类或方法 ctrl+d 复制当前光标所在行至下一行 ctrl+e 打开最近的文 ...

  8. 在项目中自定义集成IdentityService4

    OAuth2.0协议 在开始之前呢,需要我们对一些认证授权协议有一定的了解. OAuth 2.0 的一个简单解释 http://www.ruanyifeng.com/blog/2019/04/oaut ...

  9. centos 安装mysql 8.0

    在home下新建一个mysql文件夹 cd /home mkdir mysql cd mysql 1.首先上传文件,把文件传到 /home/mysql下 链接:https://pan.baidu.co ...

  10. 对list集合中元素按照某个属性进行排序

    test 为集合中的元素类型(其中包含i属性) Collections.sort(list,(test o1, test o2) -> { if (o1.getI() != o2.getI()) ...