在Golang裡如何實現結構體成員指標到結構體自身指標的轉換
在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 int
b int
f F
}
type F struct {
c int
d int
}
func (m *F) T1() *T {
var dummy *T
fieldOffset := 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 T
fieldOffset := 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 T
for i := 0; i < b.N; i++ {
if &t != t.f.T2() {
b.Fatal("wrong")
}
}
}
func BenchmarkGetPtrByMemberPtr_TBad(b *testing.B) {
var t T
for 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 int
b int
f F
e [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++那樣使用通用的巨集就可以實現。
如果你有更好的方案,歡迎留言告訴我!
相關推薦
結構體成員和結構體指標初始化
#include<stdio.h>#include <stdlib.h>#include <string>struct student{ char *name; int score; struct student* next;
C 語言通過結構體成員獲得結構體指標
通過結構體成員拿到結構體的指標,是C語言實現繼承多型的基礎。面向物件C程式設計可以參看這裡OOC 面向物件C語言程式設計實踐。這裡詳細介紹這個核心的操作方法。 /** * Get struct pointer from member pointer */ #defin
在Golang裡如何實現結構體成員指標到結構體自身指標的轉換
在C語言中有一個經典的巨集定義,可以將結構體struct內部的某個成員的指標轉化為結構體自身的指標。下面是一個例子,通過FIELD_OFFSET巨集計算結構體內一個欄位的偏移,函式getT可以從一個F*的指標獲得對應的T*物件。 struct F { int c; int d; }
C++類 給結構體成員 函式指標 賦值
myStruct標頭檔案 myStruct.h class CMyClass; struct { int nFlag; void (CMyClass::*myinit)(int n); void (CMyClass::*myopen)(int n,void* arg)
list用remove實現對結構體成員的刪除
1、使用list,首先要包含list.h標頭檔案,並使用std名稱空間 在標頭檔案中增加如下兩行說明: #include <list>using namespace std; 2、定義結構體,需要在結構體裡寫判斷“==”函式,如下所列,這裡重寫的
指向結構體的指標&結構體成員指標
1、指向結構體的指標 一個 變數的指標,就是該變數所佔據的記憶體段的起始地址。指向一個結構體的指標變數,其值是結構體變數的起始地址。 /* *copyright(c) 2018,HH *All rights reserved. *作 者:HH *完成日期:2018年8月1
關於stl::vector中儲存帶指標型別成員的結構體指標
最近用到vector中儲存結構體: struct sProc { // 程序ID int pid; // 程序狀態 int stat; // 狀態為0次數 int count; char *path; sProc() { pid = -1; stat = 0; cou
結構體指標變數與結構體成員指標變數
C程式碼 #include <stdio.h> #include <stdlib.h> #include <string.h> struct student{ char *name; int score;
[Go] golang結構體成員與函數類型
邏輯 true div ring int pac return 結構體 new package main import ( "fmt" ) //定義一個類型 type tsh struct { //定義成員,類型是func() string test func(
函數外面對單個結構體成員進行賦值出錯
類型 自動 構造 不能 結構體成員 bsp 入口 出錯 進入 關於“為什麽整型的就可以,結構體類型的就不能這麽賦值呢?”——整形等常規數據類型由編譯器自動識別,而自定義的數據類型(樓主自定義的結構體類型),編譯器在編譯階段無法識別,故出錯。 關於“在函數外單個初始化”——在
3.c語音結構體成員內存對齊詳解
定義 pre 形狀 sed 兩個 分配 我們 替代 images 一.關鍵一點 最關鍵的一點:結構體在內存中是一個矩形,而不是一個不規則形狀 二.編程實戰 1 #include <stdlib.h> 2 #inc
計算C結構體成員偏移量兩種方式本質上是一樣的
BE main print tdd of函數 pan color c結構體 計算 #include <stdio.h> #include <stddef.h> typedef struct test_st { char a[3];
strcpy拷貝結構體成員中的字元陣列溢位的問題
結構體定義: typedef struct env { char env_name[10]; char env_val[20]; int is_used;
C/C++結構體成員偏移量獲取
分析程式碼節選自muduo. 以下程式碼通過offsetof獲取sin_family在sockaddr_in6中的欄位偏移量. static_assert(offsetof(sockaddr_in6, sin6_family) == 0, "sin6_family offset 0"
Golang 之 面向物件struct ,定義結構體方法(二)
package main import "fmt" type treeNode struct { value int left, right * treeNode } // 給結構體定義列印方法,其中(node treeNode)表示該方法的接受者是那個結構體 fun
求助!結構體的二級指標陣列給一級指標初始化遇見的異常
百度也看了很多部落格都沒解決 主要程式碼如下 typedef struct HTNode { int weight; char c;//存這個字元,單個字元,符號都是葉子節點 int code; HTNode *lchild, rchild; }HuffmanTree; /
結構體成員的記憶體分佈與對齊
我們先看一道IBM和微軟的筆試題: IBM筆試題: struct{ short a1; short a2; short a3; }A; struct{ long &n
static 指標 結構體使用
static 指標 結構體使用 static使用注意事項 static --> //修飾區域性變數:將變數的生命週期變為和整個程式相同 但是不改變作用域 //修飾全域性變數:將改變作用域為當前檔案 //修飾一個函式:修改了函式的作用域為當前檔案 指標定義
@結構體陣列指向結構體變數的指標
一、結構體陣列的定義 struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; } ; [stru
C語言:存取結構體成員的點運算子(.)和箭頭運算子(->)的區別
一直以為這兩個是沒有什麼區別的,可以相互替換,今天又翻了一下《C語言核心技術》,明白了其中的奧妙。 相同點:兩個都是二元操作符,其右操作符是成員的名稱。 不同點:點操作符左邊的運算元是一個“結果為結構”的表示式; 箭頭操作符左邊的運算元是