golang数组内存分配原理
本篇文章给大家分享《golang数组内存分配原理》,覆盖了Golang的常见基础知识,其实一个语言的全部知识点一篇文章是不可能说完的,但希望通过这些问题,让读者对自己的掌握程度有一定的认识(B 数),从而弥补自己的不足,更好的掌握它。
编译时数组类型解析
ArrayType
数组是内存中一片连续的区域,在声明时需要指定长度,数组的声明有如下三种方式,[...]的方式在编译时会自动推断长度。
var arr1 [3]int
var arr2 = [3]int{1,2,3}
arr3 := [...]int{1,2,3}
在词法及语法解析时,上述三种方式声明的数组会被解析为ArrayType, 当遇到[...]的声明时,其长度会被标记为nil,将在后续阶段进行自动推断。
// go/src/cmd/compile/internal/syntax/parser.go
func (p *parser) typeOrNil() Expr {
...
pos := p.pos()
switch p.tok {
...
case _Lbrack:
// '[' oexpr ']' ntype
// '[' _DotDotDot ']' ntype
p.next()
if p.got(_Rbrack) {
return p.sliceType(pos)
}
return p.arrayType(pos, nil)
...
}
// "[" has already been consumed, and pos is its position.
// If len != nil it is the already consumed array length.
func (p *parser) arrayType(pos Pos, len Expr) Expr {
...
if len == nil && !p.got(_DotDotDot) {
p.xnest++
len = p.expr()
p.xnest--
}
...
p.want(_Rbrack)
t := new(ArrayType)
t.pos = pos
t.Len = len
t.Elem = p.type_()
return t
}
// go/src/cmd/compile/internal/syntax/nodes.go
type (
...
// [Len]Elem
ArrayType struct {
Len Expr // nil means Len is ...
Elem Expr
expr
}
...
)
types2.Array
在对生成的表达式进行类型检查时,如果是ArrayType类型,且其长度Len为nil时,会初始化一个types2.Array并将其长度标记为-1,然后通过check.indexedElts(e.ElemList, utyp.elem, utyp.len)返回数组长度n并赋值给Len,完成自动推断。
// go/src/cmd/compile/internal/types2/array.go
// An Array represents an array type.
type Array struct {
len int64
elem Type
}
// go/src/cmd/compile/internal/types2/expr.go
// exprInternal contains the core of type checking of expressions.
// Must only be called by rawExpr.
func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKind {
...
switch e := e.(type) {
...
case *syntax.CompositeLit:
var typ, base Type
switch {
case e.Type != nil:
// composite literal type present - use it
// [...]T array types may only appear with composite literals.
// Check for them here so we don't have to handle ... in general.
if atyp, _ := e.Type.(*syntax.ArrayType); atyp != nil && atyp.Len == nil {
// We have an "open" [...]T array type.
// Create a new ArrayType with unknown length (-1)
// and finish setting it up after analyzing the literal.
typ = &Array{len: -1, elem: check.varType(atyp.Elem)}
base = typ
break
}
typ = check.typ(e.Type)
base = typ
...
}
switch utyp := coreType(base).(type) {
...
case *Array:
if utyp.elem == nil {
check.error(e, "illegal cycle in type declaration")
goto Error
}
n := check.indexedElts(e.ElemList, utyp.elem, utyp.len)
// If we have an array of unknown length (usually [...]T arrays, but also
// arrays [n]T where n is invalid) set the length now that we know it and
// record the type for the array (usually done by check.typ which is not
// called for [...]T). We handle [...]T arrays and arrays with invalid
// length the same here because it makes sense to "guess" the length for
// the latter if we have a composite literal; e.g. for [n]int{1, 2, 3}
// where n is invalid for some reason, it seems fair to assume it should
// be 3 (see also Checked.arrayLength and issue #27346).
if utyp.len
types.Array
在生成中间结果时,types2.Array最终会通过types.NewArray()转换成types.Array类型。
// go/src/cmd/compile/internal/noder/types.go
// typ0 converts a types2.Type to a types.Type, but doesn't do the caching check
// at the top level.
func (g *irgen) typ0(typ types2.Type) *types.Type {
switch typ := typ.(type) {
...
case *types2.Array:
return types.NewArray(g.typ1(typ.Elem()), typ.Len())
...
}
// go/src/cmd/compile/internal/types/type.go
// Array contains Type fields specific to array types.
type Array struct {
Elem *Type // element type
Bound int64 // number of elements;
编译时数组字面量初始化
数组类型解析可以得到数组元素的类型Elem以及数组长度Bound,而数组字面量的初始化是在编译时类型检查阶段完成的,通过函数tcComplit -> typecheckarraylit循环字面量分别进行赋值。
// go/src/cmd/compile/internal/typecheck/expr.go
func tcCompLit(n *ir.CompLitExpr) (res ir.Node) {
...
t := n.Type()
base.AssertfAt(t != nil, n.Pos(), "missing type in composite literal")
switch t.Kind() {
...
case types.TARRAY:
typecheckarraylit(t.Elem(), t.NumElem(), n.List, "array literal")
n.SetOp(ir.OARRAYLIT)
...
return n
}
// go/src/cmd/compile/internal/typecheck/typecheck.go
// typecheckarraylit type-checks a sequence of slice/array literal elements.
func typecheckarraylit(elemType *types.Type, bound int64, elts []ir.Node, ctx string) int64 {
...
for i, elt := range elts {
ir.SetPos(elt)
r := elts[i]
...
r = Expr(r)
r = AssignConv(r, elemType, ctx)
...
}
编译时数组索引越界检查
在对数组进行索引访问时,如果访问越界在编译时就无法通过检查。
例如:
arr := [...]string{"s1", "s2", "s3"}
e3 := arr[3]
// invalid array index 3 (out of bounds for 3-element array)
数组在类型检查阶段会对访问数组的索引进行验证:
// go/src/cmd/compile/internal/typecheck/typecheck.go
func typecheck1(n ir.Node, top int) ir.Node {
...
switch n.Op() {
...
case ir.OINDEX:
n := n.(*ir.IndexExpr)
return tcIndex(n)
...
}
}
// go/src/cmd/compile/internal/typecheck/expr.go
func tcIndex(n *ir.IndexExpr) ir.Node {
...
l := n.X
n.Index = Expr(n.Index)
r := n.Index
t := l.Type()
...
switch t.Kind() {
...
case types.TSTRING, types.TARRAY, types.TSLICE:
n.Index = indexlit(n.Index)
if t.IsString() {
n.SetType(types.ByteType)
} else {
n.SetType(t.Elem())
}
why := "string"
if t.IsArray() {
why = "array"
} else if t.IsSlice() {
why = "slice"
}
if n.Index.Type() != nil && !n.Index.Type().IsInteger() {
base.Errorf("non-integer %s index %v", why, n.Index)
return n
}
if !n.Bounded() && ir.IsConst(n.Index, constant.Int) {
x := n.Index.Val()
if constant.Sign(x)
运行时数组内存分配
数组是内存区域一块连续的存储空间。在运行时会通过mallocgc给数组分配具体的存储空间。newarray中如果数组元素刚好只有一个,则空间大小为元素类型的大小typ.size, 如果有多个元素则内存大小为n*typ.size。但这并不是实际分配的内存大小,实际分配多少内存,取决于mallocgc,涉及到golang的内存分配原理。但可以看到如果待分配的对象不超过32kb,mallocgc会直接将其分配在缓存空间中,如果大于32kb则直接从堆区分配内存空间。
// go/src/runtime/malloc.go
// newarray allocates an array of n elements of type typ.
func newarray(typ *_type, n int) unsafe.Pointer {
if n == 1 {
return mallocgc(typ.size, typ, true)
}
mem, overflow := math.MulUintptr(typ.size, uintptr(n))
if overflow || mem > maxAlloc || n 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
...
}
总结
数组在编译阶段最终被解析为types.Array类型,包含元素类型Elem和数组长度Bound
type Array struct {
Elem *Type // element type
Bound int64 // number of elements;
- 如果数组长度未指定,例如使用了语法糖
[...],则会在表达式类型检查时计算出数组长度。 - 数组字面量初始化以及索引越界检查都是在编译时类型检查阶段完成的。
- 在运行时通过
newarray()函数对数组内存进行分配,如果数组大小超过32kb则会直接分配到堆区内存。
今天关于《golang数组内存分配原理》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
golang切片原理详细解析
- 上一篇
- golang切片原理详细解析
- 下一篇
- golang协程与线程区别简要介绍
-
- 谦让的月饼
- 这篇文章真是及时雨啊,太细致了,太给力了,码住,关注作者了!希望作者能多写Golang相关的文章。
- 2023-07-02 14:06:55
-
- 快乐的萝莉
- 很棒,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢楼主分享技术文章!
- 2023-06-09 10:30:15
-
- 坦率的黄豆
- 细节满满,已收藏,感谢作者大大的这篇文章内容,我会继续支持!
- 2023-03-26 13:54:56
-
- Golang · Go教程 | 19小时前 | 并发 · Go教程 · trace.NewTask go tool trace Go runtime/trace 执行跟踪 trace.WithRegion
- Go runtime/trace 怎样按任务标记跟踪区间
- 391浏览 收藏
-
- Golang · Go教程 | 19小时前 | flag · go · Go 命令行参数 flag.TextVar encoding.TextUnmarshaler
- Go flag.TextVar 怎样把自定义类型接入命令行
- 184浏览 收藏
-
- Golang · Go教程 | 20小时前 | 错误处理 · 模板 · Go教程 · html/template · 模板渲染 Go html/template missingkey 模板缺失字段 Execute错误
- Go html/template 缺失字段的严格渲染配置与错误处理
- 371浏览 收藏
-
- Golang · Go教程 | 21小时前 | html · go · 模板 · url · Go html/template 自动转义 URL上下文 template.URL
- Go html/template 怎样避免 URL 上下文误用
- 326浏览 收藏
-
- Golang · Go教程 | 21小时前 |
- Go text/template.ParseFS 怎样加载嵌入文件模板
- 208浏览 收藏
-
- Golang · Go教程 | 21小时前 |
- Go database/sql.NamedArg 怎样给 SQL 参数命名
- 311浏览 收藏
-
- Golang · Go教程 | 21小时前 | HTTP · go · Gin · Gin ShouldBindBodyWith 请求体重复绑定 ShouldBind binding.JSON
- Gin ShouldBindBodyWith 怎么让同一个请求体绑定两次
- 134浏览 收藏
-
- Golang · Go教程 | 22小时前 | go · 文件系统 · Go os.ReadDir Go File.ReadDir Go DirEntry Go 目录遍历
- Go os.ReadDir 怎样高效读取目录条目
- 269浏览 收藏
-
- Golang · Go教程 | 22小时前 | go · Go go/types.Config Go Importer Go 类型检查 types.Check
- Go go/types.Config 怎样给类型检查接入导入器
- 222浏览 收藏
-
- Golang · Go教程 | 22小时前 |
- Go maphash.Hash 怎样用固定种子复现哈希结果
- 341浏览 收藏
-
- Golang · Go教程 | 22小时前 | Go 字节切片 encoding/binary AppendByteOrder
- Go binary.ByteOrder.Append 怎样把整数追加到现有字节切片
- 373浏览 收藏
-
- Golang · Go教程 | 22小时前 | go · IPv6 Go ipv4 netip.ParsePrefix CIDR
- Go netip.ParsePrefix 怎样解析 CIDR 字符串
- 334浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 573次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 616次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 647次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 606次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 422次使用
-
- Goreflect反射原理示例详解
- 2022-12-22 174浏览
-
- 一文带你搞懂Golang结构体内存布局
- 2022-12-22 125浏览
-
- 浅析Golang中的内存逃逸
- 2022-12-22 344浏览
-
- 一文搞懂Golang中的内存逃逸
- 2022-12-31 378浏览
-
- go熔断原理分析与源码解读
- 2022-12-30 442浏览

