一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - golang利用unsafe操作未導出變量-Pointer使用詳解

golang利用unsafe操作未導出變量-Pointer使用詳解

2020-05-17 12:05豆瓣奶茶 Golang

這篇文章主要給大家介紹了關于golang利用unsafe操作未導出變量-Pointer使用的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧

前言

unsafe.Pointer其實就是類似C的void *,在golang中是用于各種指針相互轉換的橋梁。uintptr是golang的內置類型,是能存儲指針的整型,uintptr的底層類型是int,它和unsafe.Pointer可相互轉換。uintptr和unsafe.Pointer的區別就是:unsafe.Pointer只是單純的通用指針類型,用于轉換不同類型指針,它不可以參與指針運算;而uintptr是用于指針運算的,GC 不把 uintptr 當指針,也就是說 uintptr 無法持有對象,uintptr類型的目標會被回收。golang的unsafe包很強大,基本上很少會去用它。它可以像C一樣去操作內存,但由于golang不支持直接進行指針運算,所以用起來稍顯麻煩。

切入正題。利用unsafe包,可操作私有變量(在golang中稱為“未導出變量”,變量名以小寫字母開始),下面是具體例子。

在$GOPATH/src下建立poit包,并在poit下建立子包p,目錄結構如下:

$GOPATH/src

----poit

--------p

------------v.go

--------main.go

以下是v.go的代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package p
 
import (
 "fmt"
)
 
type V struct {
 i int32
 j int64
}
 
func (this V) PutI() {
 fmt.Printf("i=%d\n", this.i)
}
 
func (this V) PutJ() {
 fmt.Printf("j=%d\n", this.j)
}

意圖很明顯,我是想通過unsafe包來實現對V的成員i和j賦值,然后通過PutI()和PutJ()來打印觀察輸出結果。

以下是main.go源代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
 
import (
 "poit/p"
 "unsafe"
)
 
func main() {
 var v *p.V = new(p.V)
 var i *int32 = (*int32)(unsafe.Pointer(v))
 *i = int32(98)
 var j *int64 = (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + uintptr(unsafe.Sizeof(int32(0)))))
 *j = int64(763)
 v.PutI()
 v.PutJ()
}

當然會有些限制,比如需要知道結構體V的成員布局,要修改的成員大小以及成員的偏移量。我們的核心思想就是:結構體的成員在內存中的分配是一段連續的內存,結構體中第一個成員的地址就是這個結構體的地址,您也可以認為是相對于這個結構體偏移了0。相同的,這個結構體中的任一成員都可以相對于這個結構體的偏移來計算出它在內存中的絕對地址。

具體來講解下main方法的實現:

?
1
var v *p.V = new(p.V)

new是golang的內置方法,用來分配一段內存(會按類型的零值來清零),并返回一個指針。所以v就是類型為p.V的一個指針。

?
1
var i *int32 = (*int32)(unsafe.Pointer(v))

將指針v轉成通用指針,再轉成int32指針。這里就看到了unsafe.Pointer的作用了,您不能直接將v轉成int32類型的指針,那樣將會panic。剛才說了v的地址其實就是它的第一個成員的地址,所以這個i就很顯然指向了v的成員i,通過給i賦值就相當于給v.i賦值了,但是別忘了i只是個指針,要賦值得解引用。

?
1
*i = int32(98)

現在已經成功的改變了v的私有成員i的值,好開心_

但是對于v.j來說,怎么來得到它在內存中的地址呢?其實我們可以獲取它相對于v的偏移量(unsafe.Sizeof可以為我們做這個事),但我上面的代碼并沒有這樣去實現。各位別急,一步步來。

?
1
var j *int64 = (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + uintptr(unsafe.Sizeof(int32(0)))))

其實我們已經知道v是有兩個成員的,包括i和j,并且在定義中,i位于j的前面,而i是int32類型,也就是說i占4個字節。所以j是相對于v偏移了4個字節。您可以用uintptr(4)或uintptr(unsafe.Sizeof(int32(0)))來做這個事。unsafe.Sizeof方法用來得到一個值應該占用多少個字節空間。注意這里跟C的用法不一樣,C是直接傳入類型,而golang是傳入值。之所以轉成uintptr類型是因為需要做指針運算。v的地址加上j相對于v的偏移地址,也就得到了v.j在內存中的絕對地址,別忘了j的類型是int64,所以現在的j就是一個指向v.j的指針,接下來給它賦值:

?
1
*j = int64(763)

好吧,現在貌視一切就緒了,來打印下:

?
1
2
v.PutI()
v.PutJ()

如果您看到了正確的輸出,那恭喜您,您做到了!

但是,別忘了上面的代碼其實是有一些問題的,您發現了嗎?

在p目錄下新建w.go文件,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package p
 
import (
 "fmt"
 "unsafe"
)
 
type W struct {
 b byte
 i int32
 j int64
}
 
func init() {
 var w *W = new(W)
 fmt.Printf("size=%d\n", unsafe.Sizeof(*w))
}

需要修改main.go的代碼嗎?不需要,我們只是來測試一下。w.go里定義了一個特殊方法init,它會在導入p包時自動執行,別忘了我們有在main.go里導入p包。每個包都可定義多個init方法,它們會在包被導入時自動執行(在執行main方法前被執行,通常用于初始化工作),但是,最好在一個包中只定義一個init方法,否則您或許會很難預期它的行為)。我們來看下它的輸出:

size=16

等等,好像跟我們想像的不一致。來手動計算一下:b是byte類型,占1個字節;i是int32類型,占4個字節;j是int64類型,占8個字節,1+4+8=13。這是怎么回事呢?這是因為發生了對齊。在struct中,它的對齊值是它的成員中的最大對齊值。每個成員類型都有它的對齊值,可以用unsafe.Alignof方法來計算,比如unsafe.Alignof(w.b)就可以得到b在w中的對齊值。同理,我們可以計算出w.b的對齊值是1,w.i的對齊值是4,w.j的對齊值也是4。如果您認為w.j的對齊值是8那就錯了,所以我們前面的代碼能正確執行(試想一下,如果w.j的對齊值是8,那前面的賦值代碼就有問題了。也就是說前面的賦值中,如果v.j的對齊值是8,那么v.i跟v.j之間應該有4個字節的填充。所以得到正確的對齊值是很重要的)。對齊值最小是1,這是因為存儲單元是以字節為單位。所以b就在w的首地址,而i的對齊值是4,它的存儲地址必須是4的倍數,因此,在b和i的中間有3個填充,同理j也需要對齊,但因為i和j之間不需要填充,所以w的Sizeof值應該是13+3=16。如果要通過unsafe來對w的三個私有成員賦值,b的賦值同前,而i的賦值則需要跳過3個字節,也就是計算偏移量的時候多跳過3個字節,同理j的偏移可以通過簡單的數學運算就能得到。

比如也可以通過unsafe來靈活取值:

?
1
2
3
4
5
6
7
8
9
10
11
12
package main
 
import (
 "fmt"
 "unsafe"
)
 
func main() {
 var b []byte = []byte{'a', 'b', 'c'}
 var c *byte = &b[0]
 fmt.Println(*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(c)) + uintptr(1))))
}

關于填充,FastCGI協議就用到了。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:https://studygolang.com/articles/13995

延伸 · 閱讀

精彩推薦
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
主站蜘蛛池模板: 亚洲瑟瑟网 | 暖暖高清日本在线 | 热99在线视频 | 欧美18一videos极品 | 高h射尿 | 无限好资源免费观看 | 色香视频在线 | 1024日韩基地 | 国产精品国产高清国产专区 | 欧美精品超清在线播放 | 美女被狂干 | 美女和男人免费网站视频 | 亚洲视频在线免费看 | 热久久天天拍天天拍热久久2018 | 欧美大片一区二区三区 | 午夜a一级毛片 | 奇米影视888四色首页 | 继的朋友无遮漫画免费观看73 | 美女扒开屁股让我桶免费 | 欧美日韩在线观看区一二 | 日本人成动漫网站在线观看 | 欧美日韩高清观看一区二区 | jk制服白丝超短裙流白浆 | 无耻之徒第十一季在线观看 | 97久久免费视频 | 免费一区在线观看 | 亚洲国产美女精品久久久久 | 精品亚洲视频在线观看 | 国内精品久久久久小说网 | 5x社区在线观看直接进入 | 国产成人在线播放 | 性xxxx欧美高清 | 欧美日韩精品乱国产538 | 情缘1完整版在线观看 | 皇上好大好硬好涨好深好爽 | 精品久久久久久久久久久久久久久 | 午夜福利电影网站鲁片大全 | 男人懂得网站 | 果冻传媒91| 嫩草影院精品视频在线观看 | 国产成人精品高清在线观看99 |