“ 不仅限于语法,使用gdb,dlv工具更深层的剖析golang的数据结构”
变量:有意义的一个数据块。
变量名:一个有意义的数据块的名字。
为什么特意会有这个章节?
golang本质是全局按照值传递的,也就是copy值,那么你必须知道你的内存对象是包含哪些内容,才能清楚掌握你在copy值的时候复制了哪些东西,这个很重要,第一部分的正文内容从这里开始。具体如下类型:
这些结构实际在地址空间栈是什么形式的实现?这里直接看地址空间的内容,以下都是以这个例子进行分析:
package main
func main () {
var n int64 = 11
var b bool = true
var s string = "test-string-1"
var a [3]bool = [3]bool{true, false, true}
var sl []int = []int{1,2,3,4}
var m map[int]string
var c chan int
var in interface{}
_, _, _, _, _, _, _, _ = n, b, s, a, sl, m, c , in
}
n:n就是一个8字节的数据块。
b:就是一个1字节的数据块。
string类型在go里是一个复合类型,s变量是一个16字节的变量。其中str字段指向字符串存储的地方。
(gdb) pt s
type = struct string {
uint8 *str;
int len;
}
换句话说,s就是代表了一个16字节的数据块。所以我们每次定义一个string变量,除了字符序列,s的本身结构是分配16个字节在栈上。
赋值语句
0x000000000044ebed <+61>: lea 0x1e81d(%rip),%rax # 0x46d411
0x000000000044ebf4 <+68>: mov %rax,0x48(%rsp)
0x000000000044ebf9 <+73>: movq $0xd,0x50(%rsp)
地址空间存储
(gdb) p/x uintptr(&s)
$9 = 0xc000030750
(gdb) x/2gx 0xc000030750
0xc000030750: 0x000000000046d411 0x000000000000000d
(gdb) x/s 0x000000000046d411
0x46d411: "test-string-1triggerRatio=value method xadd64 failedxchg64 failed}\n\tsched={pc: but progSize nmidlelocked= out of range t.npagesKey= untyped args -thread limit\nGC assist waitGC worker initMB; alloca"...
数组类型,就是在地址空间中连续的一段内存块,和c一样(旁白:和c一样,都是平坦的内存结构)。
(gdb) p &a
$13 = ([3]bool *) 0xc00003070d
(gdb) x/6bx 0xc00003070d
0xc00003070d: 0x01 0x00 0x01 0x0b 0x00 0x00
这是个复合类型,变量本身是一个管理结构(和string一样),这个管理结构管理着一段连续的内存。
(gdb) pt sl
type = struct []int {
int *array;
int len;
int cap;
}
其中,map类型和channel类型特别提一点,变量本身本质上是一个指针类型。也就是说上面我们定义了两个变量m,c,从内存分配的角度来讲,只在栈上分配了一个指针变量,并且这个指针还是nil值,所以我们经常看到 go 的一个特殊说明:slice,map,channel 这三种类型必须使用make来创建,就是这个道理。因为如果仅仅定义了类型变量,那仅仅是代表了分配了这个变量本身的内存空间,并且初始化是nil,一旦你直接用,那么就会导致非法地址引用的问题。slice 的24个字节的管理空间,map和channel的一个指针8个字节的空间。那么如果是调用了make,其实就会把下面的结构分配并初始化出来。
(gdb) pt m
type = struct hash<int, string> {
int count;
uint8 flags;
uint8 B;
uint16 noverflow;
uint32 hash0;
struct bucket<int, string> *buckets;
struct bucket<int, string> *oldbuckets;
uintptr nevacuate;
struct runtime.mapextra *extra;
} *
(gdb) pt c
type = struct hchan<int> {
uint qcount;
uint dataqsiz;
void *buf;
uint16 elemsize;
uint32 closed;
runtime._type *elemtype;
uint sendx;
uint recvx;
struct waitq<int> recvq;
struct waitq<int> sendq;
runtime.mutex lock;
} *
那么make又做了什么,make 是 golang 的关键字,编译器会自动转变成函数调用,go 语言的关键字基本都是转换成内部的函数调用,梳理golang的关键字对应的转换表:
(旁白:编译器牛逼,golang 比c 高级就高级在编译器帮你做了非常多的事情)
特别说明下interface,因为这个也是 golang 的一个特色点,你实现了interface定义的方法集,那么可以当作这个接口用,换句话说,你实现了这些行为,就是这个接口对象,怎么实现的?这个和c++的多态是很像的,和python的行为多态更像。c++是通过虚表结构来实现的多态。go实现的接口是通过interface结构来实现的。
回忆下 c++ 的多态,c++ 是静态就定义好了继承或组合关系,虚表的个数是确定的:
有继承覆盖的情况如下:
所以 c++ 的多态就一目了然,虽然我们可能不知道当前对象是那个类,当时我们通过头部的虚表指针找到对应的虚表,按照一样的偏移offset去获取到函数指针,这个对象方法自然就会是我们对象正确的方法。
go interface
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
当我们把一个对象赋值给接口,调用方法的时候,接口怎么能获取到对象正确的方法? iface 接口里 data 存放私有数据,一般是具体对象地址。关键就在 itab 结构,本质上是一个pair(interface,concrete)。
itab结构
这个结构会在两种情况下生成:
type itab struct {
inter *interfacetype
_type *_type
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
字段含义:
内存布局:
这么个结构如果是编译器生成,是存在.rodata里。
编译器静态生成 itab
举个例子:
package main
type Node interface {
Add(a, b int32) int32
Sub(a, b int64) int64
}
type SObj struct{ id int32 }
func (adder SObj) Add(a, b int32) int32 { return a + b }
func (adder SObj) Sub(a, b int64) int64 { return a - b }
func main() {
m := Node(SObj{id: 6754})
m.Add(10, 32)
}
这里通过 SObj 变量类型赋值到接口 Node. 首先呢,这个在编译期就会检查是否能够这样赋值,允许赋值的满足的要求就是:SObj实现了Node的声明的两个方法。编译通过之后呢,会生成一个二元组对pair(Node,SObj)。调用过程呢,就是先获取itab,在获取fun,根据偏移获取方法地址。看下汇编代码:
(gdb) disassemble
Dump of assembler code for function main.main:
=> 0x000000000044ec70 <+0>: mov %fs:0xfffffffffffffff8,%rcx
0x000000000044ec79 <+9>: cmp 0x10(%rcx),%rsp
0x000000000044ec7d <+13>: jbe 0x44ecf8 <main.main+136>
0x000000000044ec7f <+15>: sub $0x40,%rsp
0x000000000044ec83 <+19>: mov %rbp,0x38(%rsp)
0x000000000044ec88 <+24>: lea 0x38(%rsp),%rbp
0x000000000044ec8d <+29>: movl $0x0,0x24(%rsp)
// 根据itab, val 构造出 interface结构 (这个是)
0x000000000044ec95 <+37>: movl $0x1a62,0x24(%rsp)
0x000000000044ec9d <+45>: lea 0x2993c(%rip),%rax # 0x4785e0 <go.itab.main.SObj,main.Node> // itab的地址;编译期确定
0x000000000044eca4 <+52>: mov %rax,(%rsp)
0x000000000044eca8 <+56>: mov 0x24(%rsp),%eax // value的值6754
0x000000000044ecac <+60>: mov %eax,0x8(%rsp)
0x000000000044ecb0 <+64>: callq 0x407fd0 <runtime.convT2I32>
0x000000000044ecb5 <+69>: mov 0x18(%rsp),%rax
0x000000000044ecba <+74>: mov 0x10(%rsp),%rcx
0x000000000044ecbf <+79>: mov %rcx,0x28(%rsp) // interface m的地址
0x000000000044ecc4 <+84>: mov %rax,0x30(%rsp)
0x000000000044ecc9 <+89>: mov 0x28(%rsp),%rax
0x000000000044ecce <+94>: test %al,(%rax)
0x000000000044ecd0 <+96>: mov 0x18(%rax),%rax // 获取到SObj.Add的地址
0x000000000044ecd4 <+100>: mov 0x30(%rsp),%rcx
// 传参数 10,20
0x000000000044ecd9 <+105>: movabs $0x200000000a,%rdx
0x000000000044ece3 <+115>: mov %rdx,0x8(%rsp)
0x000000000044ece8 <+120>: mov %rcx,(%rsp)
// 调用到 SObj.Add 方法
0x000000000044ecec <+124>: callq *%rax
0x000000000044ecee <+126>: mov 0x38(%rsp),%rbp
0x000000000044ecf3 <+131>: add $0x40,%rsp
0x000000000044ecf7 <+135>: retq
0x000000000044ecf8 <+136>: callq 0x446fb0 <runtime.morestack_noctxt>
0x000000000044ecfd <+141>: jmpq 0x44ec70 <main.main>
End of assembler dump.
(gdb) p m
$3 = {tab = 0x4785e0 <SObj,main.Node>, data = 0xc00005e000}
(gdb) p &m
$4 = (main.Node *) 0xc000030778
(gdb) p $rsp + 0x28
$5 = (void *) 0xc000030778
(gdb) x/1gx 0x28 + $rsp
0xc000030778: 0x00000000004785e0
(gdb) x/1gx 0x00000000004785e0 + 0x18
0x4785f8 <go.itab.main.SObj,main.Node+24>: 0x000000000044ed70
(gdb) info symbol 0x000000000044ed70
main.(*SObj).Add in section .text of /root/go-proj/test_interface_1
查看二进制 .rodata 段
[root@bogon go-proj]# objdump -xt -j .rodata ./test_interface_1|grep SObj
00000000004785e0 g O .rodata 0000000000000028 go.itab.main.SObj,main.Node
这里还要提一点,这里定义的两个方法reciver都是变量值,而不是指针,但其实golang默认的都是按照引用操作的,reciver为指针的版本一定会生成,取值则是按照反引用取值的。所以编译器其实生成了四个函数。
000000000044ec30 g F .text 0000000000000015 main.SObj.Add
000000000044ed70 g F .text 000000000000008c main.(*SObj).Add
000000000044ec50 g F .text 0000000000000019 main.SObj.Sub
000000000044ee00 g F .text 0000000000000097 main.(*SObj).Sub
业务逻辑是在 main.(*SObj).Add和main.(*SObj).Sub 里面,另外两个是包装函数。所以,当reciver为值的时候,你传指针或者值都可以。编译期可以帮你判定搞定。当reciver为引用的时候,你只能传引用,因为编译期没法搞定,因为这种情况就是假定我们可能要修改原对象,你如果传值,值copy之后,就不是原来的值了,这就违背了基本语义。
动态生成 itab
这个主要用在接口<->接口的场景。这种情况的itab表是动态生成的,编译期是没有静态生成的。这种两个都是接口的,你就必须是动态的去找,因为只有在runtime的时候才能知道里面赋值的到底是什么具体类型。对于itab会有一个全局hash缓存表,缓存优化性能。
举个例子:
package main
type Student struct { name string }
type Ione interface { getName() string }
type Itwo interface { getName() string }
func (s Student) getName() string { return s.name }
func main() {
var i Ione
var t Itwo
s := Student{ name:"concrete obj" }
i = s
t = i // interface 2 interface
_ = t
}
其中由于 i = s 是concrete类型到interface的赋值,这里编译器可以直接生成静态的itab结构。而由于 t = i 是接口到接口的赋值,这个编译器是无法去生成静态itab的(不要看到这里的简单例子,觉得自己肉眼可以在编译期间确定静态itab表。如果放到项目的大环境,比如纯操作接口的一个函数里面,你是无法分析出interface是否绑定到某个对象)。
(旁白:动态的有点是更灵活,缺点是现场性能损耗)
t = i 是调用 convI2I 转换函数生成 itab <Itwo, Student>。
关键路径:
convI2I -> getitab
-> new itab & itab.init
-> itabAdd
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/qtQoZaX_SJi6_TD-uGUPWA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。