2
头图

Original link: Which of the new and make do you use to allocate memory in Go?

Preface

Hello, everyone, I’m the pigeon asong . Because 5.1 went to find a girlfriend, I have never had time to write an article, thinking about it, I will hurry up and study, but I am still immersed in the sweet life of 5.1. Sure enough, the woman affected my speed of drawing the knife, but I like it very much, slightly.

Go sprinkling dog food. Let’s start to get into the topic. Today, let’s discuss how to use make and new in the 060a49b6197125 language. How are they different?

Allocate memory new

Official document definition:

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

The translation is: new is a built-in function that allocates memory. The first parameter is a type, not a value. The returned value is a pointer to the newly allocated zero value of the type.
We usually need to allocate memory space when we use pointers. Direct use of pointers with unallocated memory space will cause the program to crash, such as this:

var a *int64
*a = 10

We declare a pointer variable and use it directly, and the program will trigger panic . Because now the pointer variable a does not have a block address in the memory belonging to it, the pointer variable cannot be used directly, so the function of new Now, new , there is no problem:

var a *int64 = new(int64)
    *a = 10

The above example, we are against a common type int64 be new treatment, if it is a composite type, use new look like it? Let's look at an example:

func main(){
    // 数组
    array := new([5]int64)
    fmt.Printf("array: %p %#v \n", &array, array)// array: 0xc0000ae018 &[5]int64{0, 0, 0, 0, 0}
    (*array)[0] = 1
    fmt.Printf("array: %p %#v \n", &array, array)// array: 0xc0000ae018 &[5]int64{1, 0, 0, 0, 0}
    
    // 切片
    slice := new([]int64)
    fmt.Printf("slice: %p %#v \n", &slice, slice) // slice: 0xc0000ae028 &[]int64(nil)
    (*slice)[0] = 1
    fmt.Printf("slice: %p %#v \n", &slice, slice) // panic: runtime error: index out of range [0] with length 0

    // map
    map1 := new(map[string]string)
    fmt.Printf("map1: %p %#v \n", &map1, map1) // map1: 0xc00000e038 &map[string]string(nil)
    (*map1)["key"] = "value"
    fmt.Printf("map1: %p %#v \n", &map1, map1) // panic: assignment to entry in nil map

    // channel
    channel := new(chan string)
    fmt.Printf("channel: %p %#v \n", &channel, channel) // channel: 0xc0000ae028 (*chan string)(0xc0000ae030) 
    channel <- "123" // Invalid operation: channel <- "123" (send to non-chan type *chan string) 
}

It can be seen from the running results that after we use the new function to allocate memory, only the array can be used directly after initialization. slice , map , chan can not be used after initialization, which will trigger panic , this is because of the basic data structure of slice , map , chan It is a struct , which means that the member variables in it have not been initialized yet, so their initialization should be done with make . 060a49b6197265 will initialize their internal structure. We will make Let's go back to the struct 's look at an example first:

type test struct {
    A *int64
}

func main(){
    t := new(test)
    *t.A = 10  // panic: runtime error: invalid memory address or nil pointer dereference
             // [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a89fd]
    fmt.Println(t.A)
}

From the results of the operation new() function to initialize the structure, we only initialized the struct type, and its member variables are not initialized. Therefore, it is not recommended to use the new function for the initialization structure. The use of key-value pairs for initialization is more effective. good.

In fact, the new function is relatively rare in the daily engineering code, because it can be replaced, and it is more convenient and convenient to T{}

Initialize the built-in structure make

As we mentioned in the previous section, the make function specifically supports the memory creation of three data types: slice , map , and channel

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//    Slice: The size specifies the length. The capacity of the slice is
//    equal to its length. A second integer argument may be provided to
//    specify a different capacity; it must be no smaller than the
//    length. For example, make([]int, 0, 10) allocates an underlying array
//    of size 10 and returns a slice of length 0 and capacity 10 that is
//    backed by this underlying array.
//    Map: An empty map is allocated with enough space to hold the
//    specified number of elements. The size may be omitted, in which case
//    a small starting size is allocated.
//    Channel: The channel's buffer is initialized with the specified
//    buffer capacity. If zero, or the size is omitted, the channel is
//    unbuffered.
func make(t Type, size ...IntegerType) Type

Probably translate the top paragraph: make built-in function allocates and initializes an object of type slice , map or chan Like the new function, the first parameter is the type, not the value. Unlike new , make the same as the type of its parameter, not a pointer to it. The result depends on the type of incoming.

Using make initialize the incoming type is also different, which can be distinguished as follows:

Func             Type T     res
make(T, n)       slice      slice of type T with length n and capacity n
make(T, n, m)    slice      slice of type T with length n and capacity m

make(T)          map        map of type T
make(T, n)       map        map of type T with initial space for approximately n elements

make(T)          channel    unbuffered channel of type T
make(T, n)       channel    buffered channel of type T, buffer size n

Different types of initialization can use different postures. The main difference is the specification of length (len) and capacity (cap). Some types have no capacity, so naturally they cannot be specified. If you determine the length and capacity, you can save memory space.

Write a simple example:

func main(){
    slice := make([]int64, 3, 5)
    fmt.Println(slice) // [0 0 0]
    map1 := make(map[int64]bool, 5)
    fmt.Println(map1) // map[]
    channel := make(chan int, 1)
    fmt.Println(channel) // 0xc000066070
}

There is a point to note here, that is slice initialized, it will give a value of zero by default. Pay attention to this problem in development. I made this mistake, which resulted in inconsistent data.

Summary of the difference between new and make

  • new function is mainly to apply for a piece of memory space for the type, and return a pointer to the execution memory
  • make function can allocate and initialize the memory space and structure required by the type, and return the composite type itself.
  • make function only supports channel , map , and slice . Other types cannot use make .
  • new function is rarely used in daily development and can be replaced.
  • make function initialization slice will initialize the zero value. Pay attention to this problem in daily development.

make function bottom layer implementation

I was more curious make underlying implementation is what, so execute assembly instructions: go tool compile -N -l -S file.go , we can see make function initializes slice , map , chan each call is runtime.makeslice , runtime.makemap_small , runtime.makechan these three methods, because of the different types of underlying data structures Different, so the initialization method is also different. Let's just look at slice . Let everyone see the others. In fact, they are all similar.

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)
}

The function of this function is actually relatively simple:

  • Check whether the memory space occupied by the slice overflows.
  • Call mallocgc to apply for a piece of contiguous memory on the heap.

Checking the memory space here is calculated based on the slice capacity. The current memory space size is obtained according to the product of the current slice element size and the slice capacity. There are four conditions for checking overflow:

  • The size of the memory space has overflowed
  • The requested memory space is greater than the maximum allocatable memory
  • The incoming len smaller than 0 , and cap is only smaller than len

mallocgc implementation of the 060a49b619762c function is more complicated. I haven't understood it yet, but it is not very important. If you are interested, you can learn by yourself.

new function bottom layer implementation

new bottom layer of the 060a49b619765f function is mainly to call runtime.newobject :

// implementation of new builtin
// compiler (both frontend and SSA backend) knows the signature
// of this function
func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

The internal implementation is to directly call the mallocgc function to apply for memory on the heap, and the return value is a pointer type.

to sum up

In today's article, we mainly introduce the make and new and their differences. In fact, they are all used to allocate memory, but the make function is slice , map , chan these three types of services. Pay attention to the zero value problem when make initialize slice in daily development p0 accident.

, this article is over now. The triple quality (sharing, liking, and watching) is the motivation for the author to continue to create more high-quality content! My name is asong . See you in the next issue.

created a Golang learning exchange group, welcome you all to join the group, let’s learn and communicate together. Ways to join the group: follow the official account to obtain. For more learning materials, please go to the public account to receive.

Recommend previous articles:


asong
605 声望906 粉丝