在Golang裡如何?結構體成員指標到結構體自身指標的轉換

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

原文地址:http://goworldgs.com/?p=37

在C語言中有一個經典的宏定義,可以將結構體struct內部的某個成員的指標轉化為結構體自身的指標。下面是一個例子,通過FIELD_OFFSET宏計算結構體內一個欄位的位移,函數getT可以從一個F*的指標獲得對應的T*對象。

struct F {    int c;    int d;} struct T{    int a;    int b;    struct F f;} #define FIELD_OFFSET(type, field) ((int)(unsigned char *)(((struct type *)0)->field)) struct T* getT(struct F* f) {    return (T*)((unsigned char *)f - FIELD_OFFSET(T, F))}


在Golang中能否實現同樣的功能?嘗試寫如下的代碼:

type T struct {a intb intf F}type F struct {c intd int}func (m *F) T1() *T {var dummy *TfieldOffset := uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(dummy))return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))}

編譯通過,運行!panic: runtime error: invalid memory address or nil pointer dereference。這裡dummy *T是nil,雖然代碼並不訪問dummy所指向的內容,但是Golang依然不允許這樣使用這個指標。

既然Golang不允許使用nil指標,那麼我們可以通過建立一個無用的T對象來繞開這個問題,代碼如下:

func (m *F) T2() *T {var dummy TfieldOffset := uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(&dummy))return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))}

測試證明這個代碼可以正常工作,並且我們可以使用另外一個函數TBad來進行效能對比:

func (m *F) TBad() *T {return (*T)(unsafe.Pointer(uintptr(unsafe.Pointer(m)) - 16))} func BenchmarkGetPtrByMemberPtr_T2(b *testing.B) {var t Tfor i := 0; i < b.N; i++ {if &t != t.f.T2() {b.Fatal("wrong")}}} func BenchmarkGetPtrByMemberPtr_TBad(b *testing.B) {var t Tfor i := 0; i < b.N; i++ {if &t != t.f.TBad() {b.Fatal("wrong")}}}

測試結果:T2和TBad的運行開銷分別為:1.44 ns/op和0.85 ns/op。

考慮到T2為什麼會比TBad有更大的開銷,我們懷疑T2裡每次都需要在heap上建立一個T對象。如果T對象的大小很大的時候,建立T對象的開銷也會增大,我們可以通過增大結構體T的大小來進行驗證。我們將T結構體的定義修改為:

type T struct {a intb intf Fe [1024]byte}

再次運行發現T2的開銷增大到37.8 ns/op。那麼如何才能消除T結構體大小對這個函數的影響?Golang不允許我們使用nil指標,是不是我們只需要偽造一個*T的非nil指標即可?嘗試寫如下代碼並進行測試:

func (m *F) T3() *T {var x struct{}dummy := (*T)(unsafe.Pointer(&x))fieldOffset := uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(dummy))return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))}


T3的開銷降低到1.14 ns/op,接近最快的TBad的0.85 ns/op。更進一步的,我們可以直接使用*F指標作為dummy,代碼如下:

func (m *F) T4() *T {dummy := (*T)(unsafe.Pointer(m))fieldOffset := uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(dummy))return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))}


但是測試表明T4和T3的開銷完全一樣,都是1.14 ns/op。

從目前為止,T3和T4的實現效能非常好,只比TBad裡高一點點。推測原因是TBad不需要計算F類型field的位移,在C語言裡FIELD_OFFSET宏也是在編譯時間進行計算,但是在T3和T4中需要計算一次f *F欄位在T結構體中的位移。我們可以使用一個全域變數來儲存欄位的位移,這樣就不需要每次都進行計算,代碼如下:

var fieldOffset uintptr func init() {dummy := (*T)(unsafe.Pointer(&fieldOffset))fieldOffset = uintptr(unsafe.Pointer(&dummy.f)) - uintptr(unsafe.Pointer(dummy))}func (m *F) T5() *T {return (*T)(unsafe.Pointer((uintptr)(unsafe.Pointer(m)) - fieldOffset))}

測試表明T5的開銷和TBad一樣,都是0.85 ns/op,這個應該已經是極限了。

由於Go語言沒有提供泛型機制,所以每個需要用到這個功能的類都需要定義自己的轉換函式,而不能像C/C++那樣使用通用的宏就可以實現。

如果你有更好的方案,歡迎留言告訴我!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.