深入理解Golang make和new的区别及实现原理

目录
  • 前言
  • new的使用
    • 底层实现
  • make的使用
    • 底层实现
  • 总结

前言

在Go语言中,有两个比较雷同的内置函数,分别是newmake方法,二者都可以用来分配内存,那他们有什么区别呢?对于初学者可能会觉得有点迷惑,尤其是在掌握不牢固的时候经常遇到panic,下面我们就从底层来分析一下二者的不同。感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

new的使用

new可以对类型进行内存创建和初始化,其返回值是所创建类型的指针引用,这是与make函数的区别之一。我们通过一个示例代码看下:

func main() {
    var a *int
    fmt.Println(a) // nil
    *a = 123 //panic
    fmt.Println(a)
}

通过上面代码可以看出,当我们通过var声明一个变量后打印后输出nil,当我们给这个变量赋值的时候会报错:

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

综上可以总结出初始化一个指针变量,其值为nil,nil的值是不能直接赋值的。

既然我们知道了没有为其分配内存,那么我们使用new分配一个吧。代码修改后:

func main() {
    var a *int
	a = new(int)
    fmt.Printf("a type is :%T,a point value is :%v,a value is:%v,a size is: %v\n", a, a, *a, unsafe.Sizeof(a))
	//a type is :*int,a point value is :0xc00001a0a0,a value is:0,a size is: 8
    *a = 123
    fmt.Printf("a type is :%T,a point value is :%v,a value is:%v,a size is: %v\n", a, a, *a, unsafe.Sizeof(a))
    //a type is :*int,a point value is :0xc00001a0a0,a value is:123,a size is: 8
}

通过以上示例我们可以看到new其返回一个指向新分配的类型为int的指针,指针值为0xc00001a0a0,这个指针指向的内容的值为零(zero value)。通过new进行内存分配就可以对其进行赋值。

底层实现

new函数的签名如下:

func new(Type) *Type

Type是指变量的类型,可以看到new会根据变量类型返回一个指向该类型的指针。

底层调用的是runtime.newobject申请内存空间:

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

通过调用mallocgc在堆上按照typ.size的大小申请内存,因此new只会为结构体申请一块内存空间,不会为结构体中的指针类型申请内存空间。

make的使用

make 函数也是用于内存分配的,但是和new不同,仅支持 slicemapchannel 三种数据类型的内存创建,其返回值是所创建类型的本身,而不是新的指针引用。

注意:这三种类型都是引用类型,所以没必要返回他们的指针了,必须得初始化,但是不是设置为零值。

我们通过一个示例看一下:

func test()  {
	var s *[]int
	fmt.Printf("s: %p %#v \n", &s, s) //s: 0xc00000e028 (*[]int)(nil)
	s = new([]int)
	fmt.Printf("s: %p %#v \n", &s, s) //s: 0xc00000e028 &[]int(nil)
	(*s)[0] = 8
	fmt.Printf("s: %p %#v \n", &s, s) //panic: runtime error: index out of range [0] with length 0
}

我们先用new进行初始化,会给引用类型初始化为nil,nil是不能直接赋值的。下面改为make。

func test()  {
	var s = make([]int, 5)
	fmt.Printf("s: %p %#v \n", &s, s) //s: 0xc00000c060 []int{0, 0, 0, 0, 0}
	s[0] = 8
	fmt.Printf("s: %p %#v \n", &s, s) //s: 0xc00000c060 []int{8, 0, 0, 0, 0}
}

通过以上示例输出我们可以看到,make不仅可以开辟一个内存,还能给这个内存的类型初始化其零值。同理,对于mapchannel也是同样的效果。

底层实现

make函数的签名如下:

func make(t Type, size ...IntegerType) Type

可以看到make返回的是复合类型本身。

make在申请slice内存时,底层调用的是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 {
		mem, overflow := math.MulUintptr(et.size, uintptr(len))
		if overflow || mem > maxAlloc || len < 0 {
			panicmakeslicelen()
		}
		panicmakeslicecap()
	}

	return mallocgc(mem, et, true)
}

可以看到makeslice申请内存底层调用的也是mallocgc,首先通过MulUintptr根据容量cap乘以type.siz计算出所需要内存大小,然后再分配所需内存,makemapchannel申请内存底层分别是runtime.makemap_smallruntime.makechan,也是同样调用mallocgc

总结

  • make和new都是golang用来分配内存的函数,且在堆上分配内存,make 即分配内存,也初始化内存。new只是将内存清零,并没有初始化内存。
  • make返回的还是引用类型本身;而new返回的是指向类型的指针。
  • make只能用来分配及初始化类型为slice,map,channel的数据;new可以分配任意类型的数据。

到此这篇关于深入理解Golang make和new的区别及实现原理的文章就介绍到这了,更多相关Golang make new区别内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go语言中make和new函数的用法与区别

    相同点: make和new都是用来创建分配类型内存的. 不同点: 先看下面的代码 package main<br data-filtered="filtered"> import "fmt" <br data-filtered="filtered"> func main(){ var i *int var x = 10 *i=x fmt.Println(*i) } 编译过程会出错 panic: runtime error:

  • Go语言中new()和 make()的区别详解

    概述 Go 语言中的 new 和 make 一直是新手比较容易混淆的东西,咋一看很相似.不过解释两者之间的不同也非常容易. new 的主要特性 首先 new 是内建函数,你可以从 http://golang.org/pkg/builtin/#new 这儿看到它,它的定义也很简单: 复制代码 代码如下: func new(Type) *Type 官方文档对于它的描述是: 复制代码 代码如下: 内建函数 new 用来分配内存,它的第一个参数是一个类型,不是一个值,它的返回值是一个指向新分配类型零值的

  • 深入讲解Go语言中函数new与make的使用和区别

    前言 本文主要给大家介绍了Go语言中函数new与make的使用和区别,关于Go语言中new和make是内建的两个函数,主要用来创建分配类型内存.在我们定义生成变量的时候,可能会觉得有点迷惑,其实他们的规则很简单,下面我们就通过一些示例说明他们的区别和使用,话不多说了,来一起看看详细的介绍吧. 变量的声明 var i int var s string 变量的声明我们可以通过var关键字,然后就可以在程序中使用.当我们不指定变量的默认值时,这些变量的默认值是他们的零值,比如int类型的零值是0,st

  • 浅谈Golang的new与make区别是什么

    目录 new make 小结: 区别:在go语言中,make和new都是内存的分配(堆上),但是make只用于slice.map以及channel的初始化(非零值):而new用于类型的内存分配,并且内存置为零.make返回的是引用类型本身:而new返回的是指向类型的指针. 本文操作环境:windows10系统.GO 1.11.2.thinkpad t480电脑. Go语言中new和make都是用来内存分配的原语(allocation primitives).简单的说,new只分配内存,make用

  • golang中make和new的区别示例详解

    前言 本文主要给大家介绍了关于golang中make和new区别的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍: new 和 make 都可以用来分配空间,初始化类型,但是它们确有不同. new(T) 返回的是 T 的指针 new(T) 为一个 T 类型新值分配空间并将此空间初始化为 T 的零值,返回的是新值的地址,也就是 T 类型的指针 *T,该指针指向 T 的新分配的零值. p1 := new(int) fmt.Printf("p1 --> %#v \n &quo

  • 深入理解Golang make和new的区别及实现原理

    目录 前言 new的使用 底层实现 make的使用 底层实现 总结 前言 在Go语言中,有两个比较雷同的内置函数,分别是new和make方法,二者都可以用来分配内存,那他们有什么区别呢?对于初学者可能会觉得有点迷惑,尤其是在掌握不牢固的时候经常遇到panic,下面我们就从底层来分析一下二者的不同.感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助. new的使用 new可以对类型进行内存创建和初始化,其返回值是所创建类型的指针引用,这是与make函数的区别之一.我们通过一个示例代码看下: fun

  • 文字解说Golang Goroutine和线程的区别

    目录 一.Golang Goroutine? 二.线程是什么? 三.调度的区别 1.线程调度 2.goroutine 调度 四.栈空间的区别 1.线程占用 2.goroutine 占用 五.标识的区别 1.线程标识 2.goroutine 标识 总结 Golang Goroutine和线程的区别 Golang,轻松学习 一.Golang Goroutine? 当使用者分配足够多的任务,系统能自动帮助使用者把任务分配到 CPU 上,让这些任务尽量并发运作.这种机制在 Go语言中被称为 gorout

  • 深入理解node exports和module.exports区别

    我们只需知道三点即可知道 exports 和 module.exports 的区别了: 1.exports 是指向的 module.exports 的引用 2.module.exports 初始值为一个空对象 {},所以 exports 初始值也是 {} 3.require() 返回的是 module.exports 而不是 exports 所以: • 我们通过 var name ='nswbmw'; exports.name = name; exports.sayName =function(

  • Vue3如何理解ref toRef和toRefs的区别

    目录 一.基础 1.ref 2.toRef 3.toRefs 4.最佳的使用方式 二.深入 1.为什么需要ref 2.ref为什么需要.value 3.为什么需要toRef和toRefs Vue3中新增了几种创建响应式数据的方法,其各自的作用当然也不尽相同,每一种方法都有其自己的应用场景,今天我们来聊聊什么是ref toRef和toRefs?三者在使用方式上有什么不同?最佳的使用方式是什么? 一.基础 1.ref (1) 生成值类型的响应式数据, 通过 .value修改值 <template>

  • 深入理解Golang Channel 的底层结构

    目录 make chan 发送和接收 Goroutine Pause/Resume wait empty channel Golang 使用 Groutine 和 channels 实现了 CSP(Communicating Sequential Processes) 模型,channles在 goroutine 的通信和同步中承担着重要的角色. 在GopherCon 2017 中,Golang 专家 Kavya 深入介绍了 Go Channels 的内部机制,以及运行时调度器和内存管理系统是如

  • 深入理解Golang的反射reflect示例

    目录 编程语言中反射的概念 interface 和 反射 Golang的反射reflect reflect的基本功能TypeOf和ValueOf 说明 从relfect.Value中获取接口interface的信息 已知原有类型[进行“强制转换”] 说明 未知原有类型[遍历探测其Filed] 说明 通过reflect.Value设置实际变量的值 说明 通过reflect.ValueOf来进行方法的调用 说明 Golang的反射reflect性能 小结 总结 参考链接 编程语言中反射的概念 在计算

  • 深入理解Golang Channel 的底层结构

    目录 makechan 发送和接收 GoroutinePause/Resume waitemptychannel Golang 使用 Groutine 和 channels 实现了 CSP(Communicating Sequential Processes) 模型,channles在 goroutine 的通信和同步中承担着重要的角色. 在GopherCon 2017 中,Golang 专家 Kavya 深入介绍了 Go Channels 的内部机制,以及运行时调度器和内存管理系统是如何支持

  • 深入理解golang chan的使用

    目录 前言 见真身 结构体 发送数据 接收数据 上手 定义 发送与接收 前言 之前在看golang多线程通信的时候, 看到了go 的管道. 当时就觉得这玩意很神奇, 因为之前接触过的不管是php, java, Python, js, c等等, 都没有这玩意, 第一次见面, 难免勾起我的好奇心. 所以就想着看一看它具体是什么东西. 很明显, 管道是go实现在语言层面的功能, 所以我以为需要去翻他的源码了. 虽然最终没有翻到C的层次, 不过还是受益匪浅. 见真身 结构体 要想知道他是什么东西, 没什

  • golang协程与线程区别简要介绍

    目录 一.进程与线程 二.并发与并行 三.go协程与线程 1.调度方式 2.调度策略 3.上下文切换速度 4.栈的大小 四.GMP模型 一.进程与线程 进程是操作系统资源分配的基本单位,是程序运行的实例.例如打开一个浏览器就开启了一个进程. 线程是操作系统调度到CPU中执行的基本单位.例如在浏览器里新建一个窗口就需要一个线程来进行处理. 在一般情况下,线程是进程的组成部分,一个进程可以包含多个线程.例如浏览器可以新建多个窗口. 进程中的多个线程并发执行并共享进程的内存等资源.例如多个窗口之间可以

  • 深入理解Golang channel的应用

    目录 前言 整体结构 创建 发送 接收 关闭 前言 channel是用于 goroutine 之间的同步.通信的数据结构 channel 的底层是通过 mutex 来控制并发的,但它为程序员提供了更高一层次的抽象,封装了更多的功能,这样并发编程变得更加容易和安全,得以让程序员把注意力留到业务上去,提升开发效率 channel的用途包括但不限于以下几点: 协程间通信,同步 定时任务:和timer结合 解耦生产方和消费方,实现阻塞队列 控制并发数 本文将介绍channel的底层原理,包括数据结构,c

随机推荐