C语言在Golang里什么促成结构体成员指针到结构体本人指针的转换

初稿地址:http://goworldgs.com/?p=37

在C语言中有二个经文的宏定义,能够将协会体struct内部的某部成员的指针转化为结构体本身的指针。上面是三个例证,通过FIELD_OFFSET宏总计结构体内八个字段的舞狮,函数getT能够从3个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指针,那么我们可以通过创设2个失效的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的运作费用分别为:一.4四 ns/op和0.八5 ns/op。

思量到T2为何会比TBad有更加大的费用,咱们疑心T二里老是都亟需在heap上创建一个T对象。假若T对象的深浅一点都不小的时候,成立T对象的支付也会叠加,我们得以因此增大结构体T的高低来实行验证。大家将T结构体的概念修改为:

type T struct {
    a int
    b int
    f F
    e [1024]byte
}

C语言,再一次运营发现T2的付出增大到3七.八ns/op。那么什么样才能清除T结构体大小对这些函数的熏陶?Golang不容许我们采取nil指针,是否我们只必要伪造3个*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))
}

T三的支付下降到一.1四 ns/op,接近最快的TBad的0.八五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))
}

不过测试注明T四和T叁的付出完全平等,都以一.1四 ns/op。

从如今截止,T3和T四的完结质量万分好,只比TBad里高级中学一年级小点。猜测原因是TBad不需求总结F类型田野的舞狮,在C语言里FIELD_OFFSET宏也是在编译时实行总结,可是在T三和T四中必要总结3回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))
}

测试注脚T伍的开发和TBad1样,都以0.捌5 ns/op,这一个理应早正是极限了。

由于Go语言未有提供泛型机制,所以各样供给用到那几个功用的类都急需定义本身的转换函数,而不能够像C/C++那样选择通用的宏就能够完结。

壹旦你有更加好的方案,欢迎留言告知作者!