1. 程式人生 > >【Go unsafe進階】執行空介面中的函式:除了斷言與反射,你還有更好的選擇

【Go unsafe進階】執行空介面中的函式:除了斷言與反射,你還有更好的選擇

開發十年,就只剩下這套架構體系了! >>>   

假如我們有這樣一個包:iface.go

package iface

func GetAddFunc() interface{} {
	return add
}

type i32 int32

func add(a, b i32) i32 {
	return a + b
}

希望可以在包外執行add函式,怎麼辦?此處,因為該函式簽名是不可匯出的,所以,正常思路是使用反射,程式碼可能是這樣:

import (
	"fmt"
	"iface"
	"reflect"
)

func main() {
	addIface := iface.GetAddFunc()
	ret := reflect.ValueOf(addIface).Call([]reflect.Value{
	reflect.ValueOf(1),
	reflect.ValueOf(2),
})[0].Int()
	fmt.Println(ret) // expect: 3
}

但是,執行時發現 panic 了?!錯誤資訊如下:

panic: reflect: Call using int as type iface.i32

這可咋辦?使用斷言?型別不可匯出,更加不可能! 這時,unsafe的高階用法就派上用場了。先上最終程式碼,再來分析實現思路:

import (
	"fmt"
	"iface"
	"unsafe"

	"github.com/henrylee2cn/goutil/tpack"
)

func main() {
	addIface := iface.GetAddFunc()
	ptr := tpack.Unpack(addIface).Pointer()
	add := *(*func(a, b int32) int32)(unsafe.Pointer(&ptr))
	fmt.Println(add(1, 2)) // Output: 3
}

該程式碼,使用了本人開源的unsafe進階包tpack。 它可以幫助我們拿到函式的Pointer,之後我們就能通過unsafe強轉成外部定義的等價型別func(a, b int32) int32,進而執行它。

這種方法不但適用範圍廣泛,而且執行時沒有任何效能損耗(等價於直接呼叫iface.add

那麼,讓我們瞭解一下tpack.Unpack(iface.GetAddFunc()).Pointer()這行程式碼做了什麼事情呢?

首先,addIface是介面型別,而介面型別的底層型別原型如下:

emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}

typ欄位儲存型別資訊,word欄位儲存指向資料的位置資訊。我們當前需求只關心word。通過偏移量,我們可以拿到word的值,進而拿到函式在記憶體中的起始位置,即Pointer。

  • 計算word偏移量,在64位系統中,該值為8:
ptrOffset := unsafe.Offsetof(new(emptyInterface).word)
  • 獲取word值:
word := uintptr(unsafe.Pointer(&addIface)) + ptrOffset
  • 拿到函式在記憶體中的起始位置:
ptr := *(*uintptr)(unsafe.Pointer(word))
  • 最後,進行強轉型別,由於 ptr 表示函式的起始位置,而unsafe.Pointer要求是變數的指標,因此,需要使用 &ptr 進行指標型別的強轉:
add := *(*func(a, b int32) int32)(unsafe.Pointer(&ptr))

更多有趣的unsafe進階操作,可以瞭解

相關推薦

Java執行緒池深入理解

Java併發程式設計:執行緒池的使用在前面的文章中,我們使用執行緒的時候就去建立一個執行緒,這樣實現起來非常簡便,但是就會有一個問題:如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁建立執行緒就會大大降低系統的效率,因為頻繁建立執行緒和銷燬執行緒需要時間。那

SSH高速——Hibernate 多對多映射

pen prop package 轉載 map tex test lec set   說到多對多關系。印象最深刻的就是大學的選修課。一個學生能夠選修多門課程,一門課程能夠有多個學生選修,學生所選的每一門課程還有成績。這個場景的E-R圖例如以下:  

01月05日 三周四次Python基礎

是個 快速 files 函數 true 結果 lis pre 序列 1.8 遞歸列出目錄裏的文件1.9 匿名函數 1.8 遞歸列出目錄裏的文件 #### 遍歷目錄裏的文件(不支持子目錄文件) import os for i in os.listdir(‘C:/Users

01月11日 四周四次Python基礎

顯示 進階 col super 自定義 方法總結 總結 類方法 3.1 3.1/3.2 類的繼承3.3 類的屬性總結3.4 類的方法總結 3.1/3.2 類的繼承 類的繼承 繼承是面向對象的重要特點之一 繼承關系: 繼承是相對兩個類而言的父子關系,子類繼承父類所有的公有

01月12日 四周五次Python基礎

python3.5 rc腳本(類的定義與腳本的結構)3.6 rc腳本(start方法)3.7 rc腳本(stop和status方法)3.8 rc腳本(以daemon方式啟動) 3.5 rc腳本(類的定義與腳本的結構)/3.6 rc腳本(start方法)/3.7 rc腳本(stop和status方法) imp

mongoDB查詢聚合管道(三)--表達式操作符

ips www. name tostring 作用 數組操作 操作符 data seconds https://segmentfault.com/a/1190000010910985 管道操作符的分類 管道操作符可以分為三類: 階段操作符(Stage Operators)

JavaScript深入理解JavaScriptES6的Promise的作用並實現一個自己的Promise

  1.Promise的基本使用 1 // 需求分析: 封裝一個方法用於讀取檔案路徑,返回檔案內容 2 3 const fs = require('fs'); 4 const path = require('path'); 5 6 7 /** 8 * 把一個回

菜鳥連結串列_C 結構體 共用體 列舉_遞推遞迴

座右銘 這些年我一直提醒自己一件事情,千萬不要自己感動自己。大部分人看似的努力,不過是愚蠢導致的。什麼熬夜看書到天亮,連續幾天只睡幾小時,多久沒放假了,如果這些東西也值得誇耀,那麼富士康流水線上任何一個人都比你努力多了。人難免天生有自憐的情緒,唯有時刻保持清醒,才能看清

T-SQL02.理解SQL查詢的底層原理

本系列【T-SQL】主要是針對T-SQL的總結。 一、SQL Server組成部分 1.關係引擎:主要作用是優化和執行查詢。 包含三大元件: (1)命令解析器:檢查語法和轉換查詢樹。 (2)查詢優化器:優化查詢。 (3)查詢執行器:負責執行查詢。 2.儲存引擎:管理所有資料及涉及的IO

SSH快速——struts2呼叫action的三種方式

經過前段時間對struts2的學習與實踐,總結了一下在struts2中呼叫action的三種方式。 1、直接呼叫 我前面的部落格【SSH快速進階】系列第一篇《【SSH快速進階】——strut

SSH快速——Hibernate一對一對映(one-to-one)——主鍵關聯對映

  現實生活中,有很多場景需要用到一對一對映,比如每個學生只有一個學生證,每個公民只有一張身份證等。這裡用公民-身份證來舉例說明。      在Hibernate中實現一對一對映,有兩種實現方式:1

SSH快速——struts2的模型驅動—ModelDriven

上篇部落格《SSH快速進階——struts2簡單的例項》中,處理使用者登陸的action—LoginAction為: package com.danny.user.action; public cl

SSH快速——探索Hibernate物件的三種狀態Transient、Persistent、Detached

  學習過作業系統的朋友,腦子裡肯定都會有這張程序的狀態轉換圖:      當所有條件就緒,程序被排程執行,時間片到的時候,程序被掛起,進入就緒狀態……對程序進行的不同操作會導致程序進入到不同的狀

Python(二十四)-Python函式的引數定義和可變引數

分享一下我的偶像大神的人工智慧教程!http://blog.csdn.net/jiangjunshow 也歡迎轉載我的文章,轉載請註明出處 https://blog.csdn.net/mm2zzyzzp Python進階(二十四)-Python中函式的引數定義

Java TCP/IP Socket應用程式協議訊息的成幀解析(含程式碼)

     程式間達成的某種包含了資訊交換的形式和意義的共識稱為協議,用來實現特定應用程式的協議叫做應用程式協議。大部分應用程式協議是根據由欄位序列組成的離散資訊定義的,其中每個欄位中都包含了一段以

Java8新特性介面的預設方法和靜態方法都掌握了嗎?

## 寫在前面 > 在Java8之前的版本中,介面中只能宣告常量和抽象方法,介面的實現類中必須實現介面中所有的抽象方法。而在Java8中,介面中可以宣告預設方法和靜態方法,本文,我們就一起探討下介面中的預設方法和靜態方法。 ## 介面中的預設方法 Java 8中允許介面中包含具有具體實現的方法,該

Spring註解驅動開發使用InitializingBean和DisposableBean來管理bean的生命週期真的瞭解嗎?

## 寫在前面 > 在《[【Spring註解驅動開發】如何使用@Bean註解指定初始化和銷燬的方法?看這一篇就夠了!!](https://mp.weixin.qq.com/s?__biz=Mzg3MzE1NTIzNA==&mid=2247484985&idx=1&sn=bf7e

執行程式猿執行緒(四)—— 執行緒同步

一、前言       在上一篇部落格,小編向大家介紹了執行緒的狀態,算是進一步拉開了多執行緒的面試,在這篇部落格中,小編向大家介紹一下多執行緒中常見問題有執行緒同步和執行緒通訊,這篇部落格中小編向大家

SQL03.執行計劃之旅1

聽到大牛們說執行計劃,總是很惶恐,是對知識的缺乏的惶恐,所以必須得學習執行計劃,以減少對這一塊知識的惶恐,下面是對執行計劃的第一講-理解執行計劃。 本系列【T-SQL】主要是針對T-SQL的總結。   一、為什麼需要執行計劃? (1)幫助分析 當我們想要去分析SQL語句存在很慢時,需要有一個