Go语言-基础知识-第2章-【variable变量】使用make和new创建变量并分配变量内存

本文详细解析了Go语言中new和make关键字的用法,它们分别用于不同类型内存分配:new用于所有类型,包括自定义类型,返回指针;make限于slice、map和channel,返回实际类型。通过实例和原理剖析,帮助理解这两种内存管理方式。
摘要由CSDN通过智能技术生成
  • Go语言中 newmake 是两个内置函数,主要用来创建并分配类型的内存。在我们定义变量的时候,可能会觉得有点迷惑,不知道应该使用哪个函数来声明变量,其实他们的规则很简单  
  1.  new 只分配内存,并为变量的所有属性置系统默认值
  2.  make 只能用于 slice、map 和 channel 类型的创建并完成初始化

new关键字

在Go语言中,new 函数描述如下:

 
  1. // The new built-in function allocates memory. The first argument is a type,
  2. // not a value, and the value returned is a pointer to a newly
  3. // allocated zero value of that type.
  4. func new(Type) *Type
  • new 函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针
  • new 函数会把分配的内存置为零,也就是类型的零值


【示例】使用 new 函数为变量分配内存空间。

var sum *int
sum = new(int) //分配空间
*sum = 98
fmt.Println(*sum)

当然,new 函数不仅仅能够为系统默认的数据类型,分配空间,自定义类型也可以使用 new 函数来分配空间,如下所示:

type Student struct {
    name string
    age int
}

var s *Student
s = new(Student) //分配堆空间
s.name ="dequan"

fmt.Println(s)

这里如果我们不使用 new 函数为自定义类型分配空间(将第 7 行注释),就会报错:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x80bd277]
goroutine 1 [running]:

这就是 new 函数,它返回的永远是类型的指针,指针指向分配类型的内存地址。

new 关键字返回的是指针

 

自动类型推到会自动把变量设置为指针类型

make关键字

make 也用于内存分配的,make只用于 chan、map 以及 slice 的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针

在Go语言中,make 函数的描述如下:

  1. // The make built-in function allocates and initializes an object of type
  2. // slice, map, or chan (only). Like new, the first argument is a type, not a
  3. // value. Unlike new, make's return type is the same as the type of its
  4. // argument, not a pointer to it. The specification of the result depends on
  5. // the type:
  6. // Slice: The size specifies the length. The capacity of the slice is
  7. // equal to its length. A second integer argument may be provided to
  8. // specify a different capacity; it must be no smaller than the
  9. // length, so make([]int, 0, 10) allocates a slice of length 0 and
  10. // capacity 10.
  11. // Map: An empty map is allocated with enough space to hold the
  12. // specified number of elements. The size may be omitted, in which case
  13. // a small starting size is allocated.
  14. // Channel: The channel's buffer is initialized with the specified
  15. // buffer capacity. If zero, or the size is omitted, the channel is
  16. // unbuffered.
  17. func make(t Type, size ...IntegerType) Type

通过上面的代码可以看出 make 函数的 t 参数必须是 chan(通道)、map(字典)、slice(切片)中的一个,并且返回值也是类型本身。

注意:make 函数只用于 map,slice 和 channel,并且不返回指针。如果想要获得一个显式的指针,可以使用 new 函数进行分配,或者显式地使用一个变量的地址

Go语言中的 new 和 make 主要区别如下:

  • make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据;
  • new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
  • new 分配的空间被清零。make 分配空间后,会进行初始化;
 /* 使用make创建数组切片 */
slice1 := make([]type, len)
/* 使用 make 函数创建数组 */
map_variable := make(map[key_data_type]value_data_type)
/* 使用 make 函数创建channel */
ch := make(chan int)

实现原理

接下来我们将分别介绍一下 make 和 new 在初始化不同数据结构时的具体过程,我们会从编译期间和运行时两个不同的阶段理解这两个关键字的原理。

make

我们已经了解了 make 在创建 slice、map 和 channel 的具体过程,所以在这里我们也只是会简单提及 make 相关的数据结构初始化原理。
 


在编译期的类型检查阶段,Go语言其实就将代表 make 关键字的 OMAKE 节点根据参数类型的不同转换成了 OMAKESLICE、OMAKEMAP 和 OMAKECHAN 三种不同类型的节点,这些节点最终也会调用不同的运行时函数来初始化数据结构。

new

内置函数 new 会在编译期的 SSA 代码生成阶段经过 callnew 函数的处理,如果请求创建的类型大小是 0,那么就会返回一个表示空指针的 zerobase 变量,在遇到其他情况时会将关键字转换成 newobject:

func callnew(t *types.Type) *Node {
    if t.NotInHeap() {
        yyerror("%v is go:notinheap; heap allocation disallowed", t)
    }
    dowidth(t)

    if t.Size() == 0 {
        z := newname(Runtimepkg.Lookup("zerobase"))
        z.SetClass(PEXTERN)
        z.Type = t
        return typecheck(nod(OADDR, z, nil), ctxExpr)
    }

    fn := syslook("newobject")
    fn = substArgTypes(fn, t)
    v := mkcall1(fn, types.NewPtr(t), nil, typename(t))
    v.SetNonNil(true)
    return v
}

需要提到的是,哪怕当前变量是使用 var 进行初始化,在这一阶段也可能会被转换成 newobject 的函数调用并在堆上申请内存:

func walkstmt(n *Node) *Node {
	switch n.Op {
	case ODCL:
		v := n.Left
		if v.Class() == PAUTOHEAP {
			if prealloc[v] == nil {
				prealloc[v] = callnew(v.Type)
			}
			nn := nod(OAS, v.Name.Param.Heapaddr, prealloc[v])
			nn.SetColas(true)
			nn = typecheck(nn, ctxStmt)
			return walkstmt(nn)
		}
	case ONEW:
		if n.Esc == EscNone {
			r := temp(n.Type.Elem())
			r = nod(OAS, r, nil)
			r = typecheck(r, ctxStmt)
			init.Append(r)
			r = nod(OADDR, r.Left, nil)
			r = typecheck(r, ctxExpr)
			n = r
		} else {
			n = callnew(n.Type.Elem())
		}
	}
}

当然这也不是绝对的,如果当前声明的变量或者参数不需要在当前作用域外生存,那么其实就不会被初始化在堆上,而是会初始化在当前函数的栈中并随着函数调用的结束而被销毁。

newobject 函数的工作就是获取传入类型的大小并调用 mallocgc 在堆上申请一片大小合适的内存空间并返回指向这片内存空间的指针:

func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

总结

最后,简单总结一下Go语言中 make 和 new 关键字的实现原理,make 关键字的主要作用是创建 slice、map 和 Channel 等内置的数据结构,而 new 的主要作用是为类型申请一片内存空间,并返回指向这片内存的指针。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值