原文地址: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 uintptrfunc 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++那样使用通用的宏就可以实现。

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

转载于:https://my.oschina.net/u/2560355/blog/1491843

在Golang里如何实现结构体成员指针到结构体自身指针的转换相关推荐

  1. 结构体成员地址获得结构体起始地址

    经常我们在一些开源的或者内核代码中会看到. #define TYPE_STRUCT(ptr, type, member)((type *)((char*)(ptr)-(unsigned long)(& ...

  2. 【Linux 内核 内存管理】虚拟地址空间布局架构 ③ ( 内存描述符 mm_struct 结构体成员分析 | mmap | mm_rb | task_size | pgd | mm_users )

    文章目录 一.mm_struct 结构体成员分析 1.mmap 成员 2.mm_rb 成员 3.get_unmapped_area 函数指针 4.task_size 成员 5.pgd 成员 6.mm_ ...

  3. C语言:指针的偏移步长、结构体成员的偏移量、嵌套结构体成员的偏移量、结构体的内存对齐

    文章目录 1 不同类型指针的偏移步长 2 结构体成员的偏移量 3 嵌套结构体成员的偏移量 4 结构体的内存对齐 4.1 内存对齐的原因与优点 4.2 结构体内存对齐的规则 4.3 结构体嵌套结构体时的 ...

  4. 结构体之引用结构体成员变量

    引用结构体成员变量 一个结构体包含一个或者多个成员变量,在实际使用中,就需要对其成员变量进行引用,对于一个已经定义了的结构体变量和一个指向该结构体的指针变量. 可以用一下三种情况引用结构体 成员变量. ...

  5. 初识C语言(1)(2)(3)(4) C语言入门 保姆级教程 变量 常量 字符串 转义字符 操作符 关键字 字符串 指针 函数 结构体 数组 选择语句 循环语句

    一.如何写C语言代码 1.编译器 2.创建项目 3.创建源文件 4.写代码 5.编译+链接+运行 项目名字不要汉语,不要特殊字符,不要加空格,项目路径一般为你想要的路径 C语言中,一般创建.c源文件, ...

  6. 结构体struct及计算结构体大小

    目录 1.结构体类型的声明 2. 用结构体类型来创建结构体变量并初始化 3.结构体的嵌套使用​ 4.结构体传参 5. 结构体成员的访问 1.结构体类型的声明 结构是一些值的集合,这些值称为成员变量.结 ...

  7. 结构体引用_C/C++结构体完全攻略

    结构体是一个由程序员定义的数据类型,可以容纳许多不同的数据值.在过去,面向对象编程的应用尚未普及之前,程序员通常使用这些从逻辑上连接在一起的数据组合到一个单元中.一旦结构体类型被声明并且其数据成员被标 ...

  8. linux内核重要结构体,Linux中list_head结构体相关 | 技术部落

    在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head.虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作li ...

  9. c/c++教程 - 1.10 结构体 使用typedef定义struct结构体 结构体数组 结构体指针 结构体嵌套 结构体做函数参数 结构体const

    十二.结构体 (1)结构体定义和使用 基本概念:结构体属于用户自定义的数据类型,允许用户存储不同的数据类型. 参考视频:https://www.bilibili.com/video/BV1et411b ...

  10. 【C++】结构体 - 定义和使用,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数,结构体 const

    文章目录 1. 定义和使用 2. 结构体数组 3. 结构体指针 4. 结构体嵌套结构体 5. 结构体做函数参数 6. 结构体 const 1. 定义和使用 结构体属于用户自定义的数据类型,允许用户存储 ...

最新文章

  1. 【PHPWord】插入Excel对象
  2. python udp client
  3. 动态规划走楼梯_负重爬楼梯、过草地,服贸会六足机械人展示“送水到家”
  4. Mvc过滤器的使用【转载】
  5. python柱形图代码_Python数据可视化:基于matplotlib绘制「条形图」
  6. 计算机操作系统(6):练习题
  7. 遵义大数据中心项目工程概况_中策大数据:8月建筑工程项目有哪些?建筑工程项目信息汇总...
  8. 基于YOLOv3 与CRNN的中文自然场景文字检测与识别
  9. poj 2255 Tree Recovery
  10. struts Caused by: no protocol dtd - Class: java.net.URL
  11. redis系列(三):python操作redis
  12. ym——Android从零开始(27)(山寨版微信-下)(新)
  13. 小米路由器显示DNS服务器设置错误,小米路由器dns异常怎么修复
  14. 《计算机科学概论(第12版)》—第1章1.3节海量存储器
  15. 英语单词之说文解字(7)
  16. LaTeX:使用bib插入文献
  17. QChart入门教程-绘制正弦曲线
  18. Shaders for Game
  19. 前端实现组织结构列表
  20. 2021-2027全球与中国单级压缩机控制器市场现状及未来发展趋势

热门文章

  1. idea连接oracle可插拔数据库报ORA-12505
  2. 如何搭建自己的博客网站(手把手教你搭建免费个人博客网站)
  3. Android Studio模拟器报错:Could not initialize DirectSoundCapture
  4. java反射机制的概念及原理
  5. JZ·7.8.2019
  6. 说得清的Epoll原理
  7. 数据结构之单向循环链表
  8. 如何帮女朋友快速抢到各种票!火车票,演唱会票等!
  9. PS学习笔记----图层锁定
  10. Python案例分析之客户信贷预测模型