问题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. ES小知识点

    elasticsearch.yml配置文件 network.host: _site_ # network.host设置为"_site_",表明它绑定到我们的本地电脑的IP地址 di ...

  2. kubernetes(k8s)命令大全

    状态查询 # 查看集群信息 # kubectl cluster-info Kubernetes control plane is running at https://127.0.0.1:8443 K ...

  3. 6.监控elasticsearch集群---放弃采用(获取不到数据),建议看另一篇文章:监控elasticsearch

    prometheus监控es,同样采用exporter的方案. 项目地址: elasticsearch_exporter:https://github.com/justwatchcom/elastic ...

  4. CentOS yum如何安装php7.4

    centos系统下使用yum安装php7.4正式版,当前基于WLNMP提供的一键安装包来安装 1.添加epel源 yum install epel-release 2.添加WLNMP一键安装包源 rp ...

  5. 类和实例,super()函数

    class Foo: def __init__(self, name): self.name = name def ord_func(self): """定义实例方法,至 ...

  6. PAT (Basic Level) Practice 1014 福尔摩斯的约会 分数 20

    大侦探福尔摩斯接到一张奇怪的字条: 我们约会吧! 3485djDkxh4hhGE 2984akDfkkkkggEdsb s&hgsfdk d&Hyscvnm   大侦探很快就明白了,字 ...

  7. WPF开发经验-WPF的TextBox控件的MouseDown事件不响应的解决方法

    一 问题的发现 最近项目有个需求,大概是,当点击某个TextBox时,先执行一些业务上的逻辑处理. 于是按以往思维,将TextBox的MouseDown事件关联事件处理方法,将业务处理写在方法里. 调 ...

  8. acwing349 黑暗城堡 (最短路径生成树)

    求出最短树,用乘法原理统计答案就行了(模拟prim过程). 不知道说什么了,直接上代码: 1 #include<cstring> 2 #include<iostream> 3 ...

  9. Jmeter——BeanShell 内置变量vars、props、prev的使用

    在使用Jmeter过程中,或多或少都会接触些BeanShell,它会使工具的使用,变得更灵活. Jmeter中关于BeanShell的有: 1.BeanShell Sampler 取样器:完成Bean ...

  10. Mybatis PageHelper 使用的注意事项

    什么时候会导致不安全的分页? PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的. 只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查 ...