深度探索Go语言:包装方法
问题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语言:包装方法的更多相关文章
- [转]深度探索C语言函数可变长参数
转自:http://www.cnblogs.com/chinazhangjie/archive/2012/08/18/2645475.html 一.基础部分 1.1 什么是可变长参数 可变长参数:顾名 ...
- 深度解密Go语言之Slice
目录 当我们在说 slice 时,到底在说什么 slice 的创建 直接声明 字面量 make 截取 slice 和数组的区别在哪 append 到底做了什么 为什么 nil slice 可以直接 a ...
- 深度探索C++对象模型
深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...
- 读书笔记《深度探索c++对象模型》 概述
<深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...
- Delphi深度探索-CodeSite应用指南
Delphi深度探索-CodeSite应用指南 Delphi虽然为我们提供极其强大的调试功能,查找Bug仍然是一项艰巨的工作,通常我们写代码和调试代码的所消耗的时间是大致相同的,甚至有可能更多.为了减 ...
- 柔性数组-读《深度探索C++对象模型》有感 (转载)
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
- 柔性数组-读《深度探索C++对象模型》有感
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
- 探索equals()和hashCode()方法
探索equals()和hashCode()方法 在根类Object中,实现了equals()和hashCode()这两个方法,默认: equals()是对两个对象的地址值进行的比较(即比较引用是否相同 ...
- Google资深工程师深度讲解Go语言完整教程
资源获取链接点击这里 欢迎大家来到深度讲解Go语言的课堂.本课程将从基本语法讲起,逐渐深入,帮助同学深度理解Go语言面向接口,函数式编程,错误处理,测试,并行计算等元素,并带领大家实现一个分布式爬虫的 ...
- 侯捷STL学习(七)--深度探索vector&&array
layout: post title: 侯捷STL学习(七) date: 2017-06-13 tag: 侯捷STL --- 第十六节 深度探索vector vector源码剖析 vector内存2倍 ...
随机推荐
- 发现tab换成空格不起作用,然后解决如下。
今天发现把 .vimrc 加了set expandtab之后不起作用,这个本来是把代码中的制表符换成空格,免得不同人的设置不同造成代码缩进混乱. 然后搞了半天搞不定,应该是加载了.vimrc之后又加了 ...
- Java中“指针”的解释以及对“引用”的理解
Java中"指针"的解释以及对"引用"的理解 初学Java面对对象编程,对于一些概念还真的有点难以理解,主要是因为不由自主的联系到以前学过的C语言知识,时不时的 ...
- 使用kubectl管理Kubernetes(k8s)集群:常用命令,查看负载,命名空间namespace管理
目录 一.系统环境 二.前言 三.kubectl 3.1 kubectl语法 3.2 kubectl格式化输出 四.kubectl常用命令 五.查看kubernetes集群node节点和pod负载 5 ...
- HTTPS安全加固配置最佳实践指南
转载自:https://www.bilibili.com/read/cv16067729?spm_id_from=333.999.0.0 0x02 HTTPS安全加固指南 描述: 当你的网站上了 HT ...
- 使用nginx代理nexus,不是/根路径
location /nexus/ { proxy_pass http://192.168.0.218:8081/; proxy_set_header Host $host:$server_port; ...
- 采用阿里云 yum的方式安装ceph
首先机器需要联网,并且配置网络yum源,epel源,可从阿里开源镜像站中下载源文件. 注:EPEL (Extra Packages for Enterprise Linux)是基于Fedora的一个项 ...
- 一文搞定 Spring事务
Spring 事务 上文 使用SpringJDBC 1.JDBC事务控制 不管你现在使用的是那一种ORM开发框架,只要你的核心是JDBC,那么所有的事务处理都是围绕着JDBC开展的,而JDBC之中 ...
- 「国产系统」Tubian 0.1,兼容Windows和Android的GNU/Linux系统!
Tubian 0.42已发布:https://www.cnblogs.com/tubentubentu/p/16745926.html Tubian是我的自用系统整理而成的Linux发行版,基于Deb ...
- 微信小程序开发优化
一.开发优化一 1.使用Vant Weapp 1.1 什么是Vant Weapp Vant Weapp官网链接 Vant Weapp是有赞前端团队开源的一套小程序UI组件库,助力开发者快速搭建小程序应 ...
- gorm中的关联操作详解
一对一 belong to 属于:可以理解为舔狗认为自己属于女神,而女神都不知道舔狗的存在 type Girl struct { Id int Name string } type Dog struc ...