闲谈: 在IDA7.5有插件支持反编译Go, IDA7.6已经支持的情况下, 为什么写这篇文章呢, 就算能反编译, 也是需要对照汇编的, 对于反编译出来的很多函数, 在调试时也要很快的知道哪些才是真正存储数据的地方, 增加分析效率.
https://github.com/golang/go
这里简单提一下main_main的查找
*有符号,直接ctrl+f,IDA中搜索main_main即可
*无符号:
runtime_main调用main_main
runtime_mainIDA反编译代码参考
可以利用查找那些runtime_main中的字符串在无符号的Go程序中找到runtime_main,从而找到main_main
notice:
本文的数据均在 64位 环境下得出.
可关掉编译优化
Go程序会将字符串全部存放到一块连续的内存当中,使用的时候会利用runtime_convTstring来构造真正的字符串内存格式
数组有三种存储位置,当数组内元素较少时可以直接存于栈上,较多时存于数据区,而当数据会被返回时会存于堆上
变量a的汇编如下,它直接在栈上定义并初始化:
变量b的汇编如下,它的初始值被定义在了数据段并进行拷贝初始化:
数据拷贝函数
变量c的汇编如下,尽管它和a的值一样,但是它的地址是runtime.newobject函数新返回的,该函数传入的是数据的类型指针,它将在堆上申请空间存放对象实例,返回的是新的对象指针:
经常会遇到的情况是返回一个结构体变量,然后将其赋值给newobject申请的新变量上。
下面也是这类情况
通常会有一个地址作为runtime_newobject的参数,这个地址存放的是我们将要构造的数组的总大小,比如这里qword_234820地址处存放的就是40(5 * 8)
runtime_newobject里面会调用runtime_mallocgc分配内存
再之后会将runtime_newobject返回的指向这块内存的指针,存放到当前函数栈中的一个局部变量中,以后对这个数组的操作都是通过这个局部变量来的,比如这里的rsp+168
再下方就是对那块内存赋值, 也就是对数组初始化
一个slice是一个数组某个部分的引用。
在内存中,它是一个包含3个域的结构体:
这里解释一下长度和容量的概念:
长度:下标操作的上界,如x[i]中i必须小于长度
容量:分割操作的上界,如x[i:j]中j不能大于容量
数组的slice并不会实际复制一份数据,它只是创建一个新的数据结构,包含了另外的一个指针,一个长度和一个容量数据。
比如上面的分割表达式x[1:3]
并不分配更多的数据:它只是写了一个新的slice结构的属性来引用相同的存储数据。
在例子中,长度为2,只有y[0]和y[1]是有效的索引,但是容量为4,y[0:4]是一个有效的分割表达式。
这里先创建一个数组,然后rax存放的是构造好的数组的首地址,add rax, 8之后就会指向数组的下标1对应的元素,也就是3存放的地址,也就是我们切片[1:3]的第一个元素的指针,之后
.text:00000000004A79A8 mov [rsp], rax
.text:00000000004A79AC mov qword ptr [rsp+8], 2
.text:00000000004A79B5 mov qword ptr [rsp+16], 4
这里就在内存中构造出来了一个切片的结构
调用了runtime_convTslice,返回值是一个指向新分配的内存的指针,这个新分配的内存存放的元素数量就是切片的容量,比如这里就存放了4个元素,[3, 5, 7, 11] (从切片的那个元素开始,往后取容量那么多个),所以这里就是为什么y[0:4]是一个有效的分割表达式。
slice := make([]int, len)
make的底层会调用runtime_makeslice分配Array,真正的切片结构是后面的runtime_convTslice时创建的
makeslice返回的是一个指向实际数据的指针(不含管理slice的结构体)相当于 malloc(sizeof(Type) * len)
在访问slice中元素时,一般会检测下标是否小于len,如果越界则调用runtime_panicIndex
append 的时候会检测目标 slice1.len + 1 与 slice1.cap 的大小关系
若 slice1.len + 1 > slice1.cap 则调用runtime_growslice扩容
在对slice进行append等操作时,可能会造成slice的自动扩容。其扩容时的大小增长规则是:
copy 就是复制一个新的切片
myvar的数据结构是新一个新的切片 struct.
Go语言的map使用Hash表作为底层实现,可以在 $GOROOT/src/pkg/runtime/hashmap.goc 找到它的实现,其中 [runtime.hmap](https://draveness.me/golang/tree/runtime.hmap)
是最核心的结构体,我们先来了解一下该结构体的内部字段:
这个hash结构使用的是一个可扩展哈希的算法,由hash值mod当前hash表大小决定某一个值属于哪个桶,而hash表大小是2的指数,即上面结构体中的2^B。每次扩容,会增大到上次大小的两倍。结构体中有一个buckets和一个oldbuckets是用来实现增量扩容的。正常情况下直接使用buckets,而oldbuckets为空。如果当前哈希表正在扩容中,则oldbuckets不为空,并且buckets大小是oldbuckets大小的两倍。
具体的Bucket结构如下所示:
一般fastrand和makemap连用返回一个map,它为一个指针
读字典时使用mapaccess1和mapaccess2,写字典时会使用mapassign函数,它返回一个地址,将value写入该地址,另外还比较常见的是对字典进行遍历,会使用mapiterinit和mapiternext配合。
mapaccess和mapassign的第一个参数代表字典的类型,因此能很容易知道字典操作参数和返回值的类型。
赋值
3个参数,第三个参数是key的指针,rsp + 16, 返回值是key对应的数据指针.
访问
和赋值同理,区别是这个不会为不存在的 key 创建 key pair.
比如上方在执行完
之后,map buckets是存放了一个键值对的,可以过去查看v5(h *hmap
) ,0x000000C00014FE38来查看
可以看见buckets unsafe.Pointer为byte_C00014FE68
前面8字节的是tophash数组,目前只存放了一个键值对,就只有一个存放了,hash(”France”)的高8bit,往下就是存放键值对
这里一个细节需要注意一下。不认真看可能会以为低位用于定位bucket在数组的index,那么高位就是用于key/valule在bucket内部的offset。事实上高8位不是用作offset的,而是用于加快key的比较的。
这里也有几个细节需要注意一下。
在扩容过程中,oldbucket是被冻结的,查找时会在oldbucket中查找,但不会在oldbucket中插入数据。如果在oldbucket是找到了相应的key,做法是将它迁移到新bucket后加入evalucated标记。并且还会额外的迁移另一个pair。
然后就是只要在某个bucket中找到第一个空位,就会将key/value插入到这个位置。也就是位置位于bucket前面的会覆盖后面的(类似于存储系统设计中做删除时的常用的技巧之一,直接用新数据追加方式写,新版本数据覆盖老版本数据)。找到了相同的key或者找到第一个空位就可以结束遍历了。不过这也意味着做删除时必须完全的遍历bucket所有溢出链,将所有的相同key数据都删除。所以目前map的设计是为插入而优化的,删除效率会比插入低一些。
删除元素实际上也是先查找元素,如果元素存在则把元素从相应的bucket中删除,如果不存在则什么也不做。
notice
Go调用汇编和C:https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.1.html
在讲解函数调用之前,我们讲解一下GMP结构体
并发:一个逻辑流的执行在时间上与另一个流重叠,叫并发流,多个流并发地执行被成为并发。
并行:如果两个流并发地运行在不同的处理器核或者计算机上,那么我们成为它们为并行地执行。
并行流是并发流的真子集。
多线程或多进程是并行的基本条件,但单线程也可以用协程(coroutine)做到并发。简单将Goroutine归纳为协程并不合适,因为它运行时会创建多个线程来执行并发任务,且任务单元可被调度到其它线程执行。这更像是多线程和协程的结合体,能最大限度提升执行效率,发挥多核处理器能力。
Go的并发实现非常简单,使用一个go关键字即可
Go语言虽然使用一个Go关键字即可实现并发编程,但Goroutine被调度到后端之后,具体的实现比较复杂。Go调度器组成。
G是Goroutine的缩写,相当于操作系统中的进程控制块,在这里就是Goroutine的控制结构,是对Goroutine的抽象。其中包括执行的函数指令及参数;G保存的任务对象;线程上下文切换,现场保护和现场恢复需要的寄存器(SP、IP)等信息。
stack 描述了当前 goroutine 的栈内存范围[stack.lo, stack.hi),其中 stack 的数据结构:
stackguard0 和 stackguard1 均是一个栈指针,用于扩容场景,前者用于 Go stack ,后者用于 C stack。
M是一个线程或称为Machine,所有M是有线程栈的。如果不对该线程栈提供内存的话,系统会给该线程栈提供内存(不同操作系统提供的线程栈大小不同)。当指定了线程栈,则M.stack→G.stack,M的PC寄存器指向G提供的函数,然后去执行。
P(Processor)是一个抽象的概念,并不是真正的物理CPU。所以当P有任务时需要创建或者唤醒一个系统线程来执行它队列里的任务。所以P/M需要进行绑定,构成一个执行单元。
P决定了同时可以并发任务的数量,可通过GOMAXPROCS限制同时执行用户级任务的操作系统线程。可以通过runtime.GOMAXPROCS进行指定。在Go1.5之后GOMAXPROCS被默认设置可用的核数,而之前则默认为1。
首先创建一个G对象,G对象保存到P本地队列或者是全局队列。
P此时去唤醒一个M。P继续执行它的执行序。
M寻找是否有空闲的P,如果有则将该G对象移动到它本身。
接下来M执行一个调度循环(调用G对象->执行->清理线程→继续找新的Goroutine执行)。
M执行过程中,随时会发生上下文切换。
当发生上下文切换时,需要对执行现场进行保护,以便下次被调度执行时进行现场恢复。
Go调度器M的栈保存在G对象上,只需要将M所需要的寄存器(SP、PC等)保存到G对象上就可以实现现场保护。当这些寄存器数据被保护起来,就随时可以做上下文切换了,在中断之前把现场保存起来。如果此时G任务还没有执行完,M可以将任务重新丢到P的任务队列,等待下一次被调度执行。当再次被调度执行时,M通过访问G的vdsoSP、vdsoPC寄存器进行现场恢复(从上次中断位置继续执行)。
后面分析函数的开头和结尾的时候可以对照。
Go语言支持goroutine,每个goroutine需要能够运行,所以它们都有自己的栈。假如每个goroutine分配固定栈大小并且不能增长,太小则会导致溢出,太大又会浪费空间,无法存在许多的goroutine。
为了解决这个问题,goroutine可以初始时只给栈分配很小的空间,然后随着使用过程中的需要自动地增长。这就是为什么Go可以开千千万万个goroutine而不会耗尽内存。
Go1.3版本之后则使用的是continuous stack,下面将具体分析一下这种技术。
每次执行函数调用时Go的runtime都会进行检测,若当前栈的大小不够用,则会触发“中断”,从当前函数进入到Go的运行时库,Go的运行时库会保存此时的函数上下文环境,然后分配一个新的足够大的栈空间,将旧栈的内容拷贝到新栈中,并做一些设置,使得当函数恢复运行时,函数会在新分配的栈中继续执行,仿佛整个过程都没发生过一样,这个函数会觉得自己使用的是一块大小“无限”的栈空间。
Go语言和C不同,不是使用栈指针寄存器和栈基址寄存器确定函数的栈的。
在Go的运行时库中,每个goroutine对应一个结构体G,大致相当于进程控制块的概念。
这个结构体中存了stackbase和stackguard,用于确定这个goroutine使用的栈空间信息。
每个Go函数调用的前几条指令,先比较栈指针寄存器跟g->stackguard(偏移0x10),检测是否发生栈溢出。
如果栈指针寄存器值超越了stackguard就需要扩展栈空间。
函数开头首先gs:0x28,获取到了g结构体地址
后面的g+0x10也就是g->stackguard的地址
所以是把rsp和g->stackguard的值比较
如果SP大于g->stackguard了,则会跳转到函数结尾调用runtime.morestack函数。函数开头几条指令的作用就是检测栈是否溢出。
runtime.morestack作用:
将一些信息存在M结构体中,这些信息包括当前栈桢,参数,当前函数调用,函数返回地址(两个返回地址,一个是runtime.morestack的函数地址,一个是f的返回地址)。通过这些信息可以把新栈和旧栈链起来。
无论是 x86 还是 x86-64, 都采用栈传递参数
返回值传递不通过eax/rax等寄存器,也是通过栈。
位置是最后一个参数的下面。例如最后一个参数的地址是 rsp + 0x8
, 则:
返回值都会存放到main函数的栈中,main的局部变量
golang 的一种垃圾回收机制,逆向时可以认为这个表达式永真即可,不用关注runtime_gcWriteBarrier 分支
参考:
https://tiancaiamao.gitbooks.io/go-internals/content/zh
https://blog.csdn.net/star_of_science/article/details/121802354
https://panda0s.top/2021/04/14/Golang-underlying-data-representaion/#Golang-underlying-data-representaion
https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-hashmap/
https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.1.html
https://blog.csdn.net/further_eye/article/details/112506505#t18
https://www.1024sou.com/article/287980.html
https://segmentfault.com/a/1190000040933033
void runtime_main()
{
PVOID ArbitraryUserPointer;
/
/
rax
__int64 v1;
/
/
rdx
__int64 i;
/
/
rax
__int64 v3;
/
/
[rsp
+
0h
] [rbp
-
50h
]
__int64 v4;
/
/
[rsp
+
0h
] [rbp
-
50h
]
__int64 v5;
/
/
[rsp
+
0h
] [rbp
-
50h
]
__int64 v6;
/
/
[rsp
+
10h
] [rbp
-
40h
]
__int64 v7;
/
/
[rsp
+
20h
] [rbp
-
30h
] BYREF
__int64 v8;
/
/
[rsp
+
28h
] [rbp
-
28h
]
__int64 v9;
/
/
[rsp
+
30h
] [rbp
-
20h
]
__int128 v10;
/
/
[rsp
+
38h
] [rbp
-
18h
]
void
*
retaddr;
/
/
[rsp
+
50h
] [rbp
+
0h
] BYREF
while
( (unsigned __int64)&retaddr <
=
*
(_QWORD
*
)(
*
(_QWORD
*
)NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer
+
16LL
) )
runtime_morestack_noctxt();
v10
=
0LL
;
HIBYTE(v7)
=
0
;
v9
=
*
(_QWORD
*
)NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer;
*
(_QWORD
*
)(
*
*
(_QWORD
*
*
)(v9
+
48
)
+
304LL
)
=
0LL
;
runtime_maxstacksize
=
1000000000LL
;
runtime_maxstackceiling
=
2000000000LL
;
runtime_mainStarted
=
1
;
_InterlockedExchange((volatile __int32
*
)&unk_5683A0,
1
);
runtime_systemstack((__int64)&off_4D6020);
ArbitraryUserPointer
=
NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer;
+
+
*
(_DWORD
*
)(
*
(_QWORD
*
)(
*
(_QWORD
*
)ArbitraryUserPointer
+
48LL
)
+
580LL
);
v1
=
*
(_QWORD
*
)NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer;
*
(_QWORD
*
)(
*
(_QWORD
*
)(v1
+
48
)
+
312LL
)
=
v1;
*
(_QWORD
*
)(v1
+
216
)
=
*
(_QWORD
*
)(v1
+
48
);
if
(
*
(_UNKNOWN
*
*
)(v9
+
48
) !
=
&runtime_m0 )
goto LABEL_28;
byte_568678
=
1
;
runtime_nanotime1(v3);
runtime_runtimeInitTime
=
v4;
if
( !v4 )
{
LABEL_27:
runtime_throw((__int64)
"nanotime returning zero"
,
23LL
);
LABEL_28:
runtime_throw((__int64)
"runtime.main not on m0"
,
22LL
);
runtime_deferreturn(v5);
return
;
}
if
( dword_5AB048 )
{
qword_5AAE88
=
*
(_QWORD
*
)(
*
(_QWORD
*
)NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer
+
152LL
);
runtime_inittrace
=
1
;
}
runtime_doInit((__int64)&runtime__inittask);
HIWORD(v7)
=
257
;
*
((_QWORD
*
)&v10
+
1
)
=
&off_4D6028;
*
(_QWORD
*
)&v10
=
(char
*
)&v7
+
6
;
runtime_gcenable();
v6
=
runtime_makechan((__int64)&unk_4B2580,
0LL
);
if
( runtime_writeBarrier )
runtime_gcWriteBarrier();
else
runtime_main_init_done
=
v6;
if
( !runtime_iscgo )
goto LABEL_12;
if
( !cgo_thread_start )
{
LABEL_26:
runtime_throw((__int64)
"_cgo_thread_start missing"
,
25LL
);
goto LABEL_27;
}
if
( !cgo_notify_runtime_init_done )
{
runtime_throw((__int64)
"_cgo_notify_runtime_init_done missing"
,
37LL
);
goto LABEL_26;
}
runtime_startTemplateThread();
runtime_cgocall(cgo_notify_runtime_init_done,
0LL
, v6);
LABEL_12:
runtime_doInit((__int64)&main__inittask);
runtime_inittrace
=
0
;
runtime_closechan(runtime_main_init_done);
BYTE6(v7)
=
0
;
runtime_unlockOSThread();
if
( !runtime_isarchive && !runtime_islibrary )
{
main_main();
if
( runtime_runningPanicDefers )
{
for
( i
=
0LL
; i <
1000
&& runtime_runningPanicDefers; i
=
v8
+
1
)
{
v8
=
i;
runtime_mcall();
}
}
if
( runtime_panicking )
v7
=
runtime_gopark(
0LL
,
0LL
,
4104
,
1LL
);
runtime_exit(
0
);
while
(
1
)
MEMORY[
0
]
=
0
;
}
HIBYTE(v7)
=
0
;
runtime_main_func2(v10);
}
void runtime_main()
{
PVOID ArbitraryUserPointer;
/
/
rax
__int64 v1;
/
/
rdx
__int64 i;
/
/
rax
__int64 v3;
/
/
[rsp
+
0h
] [rbp
-
50h
]
__int64 v4;
/
/
[rsp
+
0h
] [rbp
-
50h
]
__int64 v5;
/
/
[rsp
+
0h
] [rbp
-
50h
]
__int64 v6;
/
/
[rsp
+
10h
] [rbp
-
40h
]
__int64 v7;
/
/
[rsp
+
20h
] [rbp
-
30h
] BYREF
__int64 v8;
/
/
[rsp
+
28h
] [rbp
-
28h
]
__int64 v9;
/
/
[rsp
+
30h
] [rbp
-
20h
]
__int128 v10;
/
/
[rsp
+
38h
] [rbp
-
18h
]
void
*
retaddr;
/
/
[rsp
+
50h
] [rbp
+
0h
] BYREF
while
( (unsigned __int64)&retaddr <
=
*
(_QWORD
*
)(
*
(_QWORD
*
)NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer
+
16LL
) )
runtime_morestack_noctxt();
v10
=
0LL
;
HIBYTE(v7)
=
0
;
v9
=
*
(_QWORD
*
)NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer;
*
(_QWORD
*
)(
*
*
(_QWORD
*
*
)(v9
+
48
)
+
304LL
)
=
0LL
;
runtime_maxstacksize
=
1000000000LL
;
runtime_maxstackceiling
=
2000000000LL
;
runtime_mainStarted
=
1
;
_InterlockedExchange((volatile __int32
*
)&unk_5683A0,
1
);
runtime_systemstack((__int64)&off_4D6020);
ArbitraryUserPointer
=
NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer;
+
+
*
(_DWORD
*
)(
*
(_QWORD
*
)(
*
(_QWORD
*
)ArbitraryUserPointer
+
48LL
)
+
580LL
);
v1
=
*
(_QWORD
*
)NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer;
*
(_QWORD
*
)(
*
(_QWORD
*
)(v1
+
48
)
+
312LL
)
=
v1;
*
(_QWORD
*
)(v1
+
216
)
=
*
(_QWORD
*
)(v1
+
48
);
if
(
*
(_UNKNOWN
*
*
)(v9
+
48
) !
=
&runtime_m0 )
goto LABEL_28;
byte_568678
=
1
;
runtime_nanotime1(v3);
runtime_runtimeInitTime
=
v4;
if
( !v4 )
{
LABEL_27:
runtime_throw((__int64)
"nanotime returning zero"
,
23LL
);
LABEL_28:
runtime_throw((__int64)
"runtime.main not on m0"
,
22LL
);
runtime_deferreturn(v5);
return
;
}
if
( dword_5AB048 )
{
qword_5AAE88
=
*
(_QWORD
*
)(
*
(_QWORD
*
)NtCurrentTeb()
-
>NtTib.ArbitraryUserPointer
+
152LL
);
runtime_inittrace
=
1
;
}
runtime_doInit((__int64)&runtime__inittask);
HIWORD(v7)
=
257
;
*
((_QWORD
*
)&v10
+
1
)
=
&off_4D6028;
*
(_QWORD
*
)&v10
=
(char
*
)&v7
+
6
;
runtime_gcenable();
v6
=
runtime_makechan((__int64)&unk_4B2580,
0LL
);
if
( runtime_writeBarrier )
runtime_gcWriteBarrier();
else
runtime_main_init_done
=
v6;
if
( !runtime_iscgo )
goto LABEL_12;
if
( !cgo_thread_start )
{
LABEL_26:
runtime_throw((__int64)
"_cgo_thread_start missing"
,
25LL
);
goto LABEL_27;
}
if
( !cgo_notify_runtime_init_done )
{
runtime_throw((__int64)
"_cgo_notify_runtime_init_done missing"
,
37LL
);
goto LABEL_26;
}
runtime_startTemplateThread();
runtime_cgocall(cgo_notify_runtime_init_done,
0LL
, v6);
LABEL_12:
runtime_doInit((__int64)&main__inittask);
runtime_inittrace
=
0
;
runtime_closechan(runtime_main_init_done);
BYTE6(v7)
=
0
;
runtime_unlockOSThread();
if
( !runtime_isarchive && !runtime_islibrary )
{
main_main();
if
( runtime_runningPanicDefers )
{
for
( i
=
0LL
; i <
1000
&& runtime_runningPanicDefers; i
=
v8
+
1
)
{
v8
=
i;
runtime_mcall();
}
}
if
( runtime_panicking )
v7
=
runtime_gopark(
0LL
,
0LL
,
4104
,
1LL
);
runtime_exit(
0
);
while
(
1
)
MEMORY[
0
]
=
0
;
}
HIBYTE(v7)
=
0
;
runtime_main_func2(v10);
}
-
gcflags
"-N -l"
go build
-
o hello.exe
-
gcflags
"-N -l"
hello.go
-
gcflags
"-N -l"
go build
-
o hello.exe
-
gcflags
"-N -l"
hello.go
struct String{
char
*
strPtr;
/
/
指向目标字符串的开始地址
int64 size;
/
/
字符串大小
}
struct String{
char
*
strPtr;
/
/
指向目标字符串的开始地址
int64 size;
/
/
字符串大小
}
func ArrDemo()
*
[
3
]
int
{
a :
=
[...]
int
{
1
,
2
,
3
}
b :
=
[...]
int
{
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
1
,
2
,
3
,
4
,
5
,
6
,
7
}
c :
=
[...]
int
{
1
,
2
,
3
}
if
len
(a) <
len
(b) {
return
&c}
return
nil
}
func ArrDemo()
*
[
3
]
int
{
a :
=
[...]
int
{
1
,
2
,
3
}
b :
=
[...]
int
{
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
1
,
2
,
3
,
4
,
5
,
6
,
7
}
c :
=
[...]
int
{
1
,
2
,
3
}
if
len
(a) <
len
(b) {
return
&c}
return
nil
}
type
arrayHeader struct {
Data uintptr
Len
int
}
type
arrayHeader struct {
Data uintptr
Len
int
}
x :
=
[]
int
{
1
,
2
,
3
,
4
,
5
}
x :
=
[]
int
{
1
,
2
,
3
,
4
,
5
}
y :
=
[]float32{
1000.0
,
2.0
,
3.4
,
7.0
,
50.0
}
y :
=
[]float32{
1000.0
,
2.0
,
3.4
,
7.0
,
50.0
}
{
指向
slice
中第一个元素的指针
slice
的长度
slice
的容量
}
{
指向
slice
中第一个元素的指针
slice
的长度
slice
的容量
}
slice1 :
=
make([]
int
,
5
)
slice1[
3
]
=
66
slice1 :
=
make([]
int
,
5
)
slice1[
3
]
=
66
/
/
runtime_makeslice
func makeslice(et
*
_type,
len
, cap
int
) unsafe.Pointer {
mem, overflow :
=
math.MulUintptr(et.size, uintptr(cap))
if
overflow || mem > maxAlloc ||
len
<
0
||
len
> cap {
/
/
NOTE: Produce a
'len out of range'
error instead of a
/
/
'cap out of range'
error when someone does make([]T, bignumber).
/
/
'cap out of range'
is
true too, but since the cap
is
only being
/
/
supplied implicitly, saying
len
is
clearer.
/
/
See golang.org
/
issue
/
4085.
mem, overflow :
=
math.MulUintptr(et.size, uintptr(
len
))
if
overflow || mem > maxAlloc ||
len
<
0
{
panicmakeslicelen()
}
panicmakeslicecap()
}
return
mallocgc(mem, et, true)
}
/
/
runtime_makeslice
func makeslice(et
*
_type,
len
, cap
int
) unsafe.Pointer {
mem, overflow :
=
math.MulUintptr(et.size, uintptr(cap))
if
overflow || mem > maxAlloc ||
len
<
0
||
len
> cap {
/
/
NOTE: Produce a
'len out of range'
error instead of a
/
/
'cap out of range'
error when someone does make([]T, bignumber).
/
/
'cap out of range'
is
true too, but since the cap
is
only being
/
/
supplied implicitly, saying
len
is
clearer.
/
/
See golang.org
/
issue
/
4085.
mem, overflow :
=
math.MulUintptr(et.size, uintptr(
len
))
if
overflow || mem > maxAlloc ||
len
<
0
{
panicmakeslicelen()
}
panicmakeslicecap()
}
return
mallocgc(mem, et, true)
}
slice1
=
append(slice1,
123
)
slice1
=
append(slice1,
123
)
myvar :
=
slice1[
1
:
3
]
type
hmap struct {
count
int
/
/
map
中键值对数量
flags uint8
/
/
map
当前是否处于写入状态登
B uint8
/
/
2
的B次幂表示当前
map
中桶的数量
noverflow uint16
/
/
map
中溢出桶的数量,当溢出桶太多时,
map
会进行等量扩容
hash0 uint32
/
/
生成
hash
的随机数种子
buckets unsafe.Pointer
/
/
当前
map
对应的桶的指针
oldbuckets unsafe.Pointer
/
/
map
扩容时指向旧桶的指针,当所有旧桶中的数据转移到新桶时,清空
nevacuate uintptr
/
/
扩容时,用于标记当前旧桶中小于nevacute的数据都已经转移到了新桶
extra
*
mapextra
/
/
存储
map
的溢出桶
}
type
mapextra struct {
overflow
*
[]
*
bmap
oldoverflow
*
[]
*
bmap
nextOverflow
*
bmap
}
type
hmap struct {
count
int
/
/
map
中键值对数量
flags uint8
/
/
map
当前是否处于写入状态登
B uint8
/
/
2
的B次幂表示当前
map
中桶的数量
noverflow uint16
/
/
map
中溢出桶的数量,当溢出桶太多时,
map
会进行等量扩容
hash0 uint32
/
/
生成
hash
的随机数种子
buckets unsafe.Pointer
/
/
当前
map
对应的桶的指针
oldbuckets unsafe.Pointer
/
/
map
扩容时指向旧桶的指针,当所有旧桶中的数据转移到新桶时,清空
nevacuate uintptr
/
/
扩容时,用于标记当前旧桶中小于nevacute的数据都已经转移到了新桶
extra
*
mapextra
/
/
存储
map
的溢出桶
}
type
mapextra struct {
overflow
*
[]
*
bmap
oldoverflow
*
[]
*
bmap
nextOverflow
*
bmap
}
type
bmap struct {
tophash [
8
]uint8
/
/
存储
Hash
值的高
8
位
data []byte
/
/
key value数据:key
/
key
/
key...
/
value
/
value
/
value...
overflow
*
bmap
/
/
溢出bucket的地址
}
type
bmap struct {
tophash [
8
]uint8
/
/
存储
Hash
值的高
8
位
data []byte
/
/
key value数据:key
/
key
/
key...
/
value
/
value
/
value...
overflow
*
bmap
/
/
溢出bucket的地址
}
func fastrand() uint32
func makemap(mapType
*
byte, hint
int
, mapbuf
*
any
) (hmap
map
[
any
]
any
)
func mapaccess1(mapType
*
byte, hmap
map
[
any
]
any
, key
*
any
) (val
*
any
)
func mapaccess2(mapType
*
byte, hmap
map
[
any
]
any
, key
*
any
) (val
*
any
, pres
bool
)
func mapassign(mapType
*
byte, hmap
map
[
any
]
any
, key
*
any
) (val
*
any
)
func mapiterinit(mapType
*
byte, hmap
map
[
any
]
any
, hiter
*
any
)
func mapiternext(hiter
*
any
)
func fastrand() uint32
func makemap(mapType
*
byte, hint
int
, mapbuf
*
any
) (hmap
map
[
any
]
any
)
func mapaccess1(mapType
*
byte, hmap
map
[
any
]
any
, key
*
any
) (val
*
any
)
func mapaccess2(mapType
*
byte, hmap
map
[
any
]
any
, key
*
any
) (val
*
any
, pres
bool
)
func mapassign(mapType
*
byte, hmap
map
[
any
]
any
, key
*
any
) (val
*
any
)
func mapiterinit(mapType
*
byte, hmap
map
[
any
]
any
, hiter
*
any
)
func mapiternext(hiter
*
any
)
func mapassign(t
*
maptype, h
*
hmap, key unsafe.Pointer) unsafe.Pointer{}
func mapassign(t
*
maptype, h
*
hmap, key unsafe.Pointer) unsafe.Pointer{}
func mapaccess1(t
*
maptype, h
*
hmap, key unsafe.Pointer) unsafe.Pointer {}
func mapaccess1(t
*
maptype, h
*
hmap, key unsafe.Pointer) unsafe.Pointer {}
package main
import
"fmt"
func main() {
countryCapitalMap :
=
map
[string]string{
"France"
:
"Paris"
,
"Italy"
:
"Rome"
,
"Japan"
:
"Tokyo"
,
"India"
:
"New delhi"
}
fmt.Println(
"France首都是"
, countryCapitalMap [
"France"
])
}
package main
import
"fmt"
func main() {
countryCapitalMap :
=
map
[string]string{
"France"
:
"Paris"
,
"Italy"
:
"Rome"
,
"Japan"
:
"Tokyo"
,
"India"
:
"New delhi"
}
fmt.Println(
"France首都是"
, countryCapitalMap [
"France"
])
}
package main
import
"fmt"
func main() {
var countryCapitalMap
map
[string]string
/
*
创建集合, 默认
map
是nil
*
/
/
/
如果不初始化
map
,那么就会创建一个 nil
map
, nil
map
不能用来存放键值对
countryCapitalMap
=
make(
map
[string]string)
countryCapitalMap [
"France"
]
=
"巴黎"
countryCapitalMap [
"Italy"
]
=
"罗马"
countryCapitalMap [
"Japan"
]
=
"东京"
countryCapitalMap [
"India "
]
=
"新德里"
/
/
或者:countryCapitalMap :
=
map
[string]string{
"France"
:
"Paris"
,
"Italy"
:
"Rome"
,
"Japan"
:
"Tokyo"
,
"India"
:
"New delhi"
}
/
*
使用键输出地图值
*
/
for
country :
=
range
countryCapitalMap {
fmt.Println(country,
"首都是"
, countryCapitalMap [country])
}
}
package main
import
"fmt"
func main() {
var countryCapitalMap
map
[string]string
/
*
创建集合, 默认
map
是nil
*
/
/
/
如果不初始化
map
,那么就会创建一个 nil
map
, nil
map
不能用来存放键值对
countryCapitalMap
=
make(
map
[string]string)
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-4-17 13:42
被SYJ-Re编辑
,原因: 更新