问题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. Windows平台Unity3d播放多路RTMP或RTSP流

    好多开发者在做AR.VR或者教育类产品时,苦于如何在windows平台构建一个稳定且低延迟的RTSP或者RTMP播放器,如果基于Unity3d完全重新开发一个播放器,代价大.而且周期长,不适合快速出产 ...

  2. KingbaseES 中实现mysql的from_days和to_days

    mysql中两个函数的说明: TO_DAYS(date)给出一个日期date,返回一个天数. FROM_DAYS(N)给出一个天数N,返回一个DATE值. 两个函数比较计算的日期都是 0000-01- ...

  3. Jmeter处理响应报文中文乱码

    Jmeter在访问发送请求的时候,响应内容如果有中文可能会显示乱码,原因应该是响应页面没有做编码处理,jmeter默认按照ISO-8859-1编码格式进行解析.而我们的响应报文却是utf-8的格式,所 ...

  4. (数据科学学习手札142)dill:Python中增强版的pickle

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,相信不少读者朋友们都在Pyth ...

  5. 《Java基础——线程类》

    Java基础--线程类       一.线程的创建之Thread类: 规则: 通过声明一个新类作为子类继承 Thread 类,并复写 run() 方法,就可以启动新线程并执行自己定义的 run()方法 ...

  6. 【项目实战】pytorch实现逻辑斯蒂回归

    视频指导:https://www.bilibili.com/video/BV1Y7411d7Ys?p=6 一些数据集 在pytorch框架下,里面面有配套的数据集,pytorch里面有一个torchv ...

  7. UEC++ 多线程(一) FRunnable

    虚幻官方文档:https://docs.unrealengine.com/5.0/en-US/API/Runtime/Core/HAL/FRunnable/ FRunnable "runna ...

  8. harbor官方关于创建https的有关命令

    官方地址:https://goharbor.io/docs/2.0.0/install-config/configure-https/ 命令总结: openssl genrsa -out ca.key ...

  9. k8s中的ingress使用上层负载均衡进行设置访问

    注意:这种情况下需要有个前提条件,也就是ingress-nginx-controller安装后的service是NodePort或者hostNetwork模式,而不能是ClusterIP,因为负载均衡 ...

  10. 学习ASP.NET Core Blazor编程系列五——列表页面

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...