[Golang] Golang Advanced Series Tutorial--Why is the value address of Go for-range the same every time?

foreword

Loop statement is a commonly used control structure. In Go language, in addition to the for keyword, there is also a range keyword. You can use the for-range loop to iterate data types such as arrays, slices, strings, maps, and channels.

But when using the for-range loop to iterate arrays and slices, it is easy to make mistakes, and even many old drivers will overturn here accidentally.

How exactly? Let's move on.

Phenomenon

Let's look at two interesting pieces of code:

Infinite loop

If we add elements to the array while iterating over it, can we get a loop that never stops?
For example, the following code:

func main() {
    
    
    arr := []int{
    
    1, 2, 3}
    for _, v := range arr {
    
    
        arr = append(arr, v)
    }
    fmt.Println(arr)
}

Program output:

$ go run main.go
1 2 3 1 2 3

The output of the above code means that the loop only traversed three elements in the original slice, and the elements we appended while traversing the slice did not increase the number of executions of the loop, so the loop eventually stopped.

same address

The second example is a common mistake when using the Go language.

When we are traversing an array, if we get the address of the variable returned by range and save it to another array or hash, we will encounter a confusing phenomenon:

func main() {
    
    
    arr := []int{
    
    1, 2, 3}
    newArr := []*int{
    
    }
    for _, v := range arr {
    
    
        newArr = append(newArr, &v)
    }
    for _, v := range newArr {
    
    
        fmt.Println(*v)
    }
}

Program output:

$ go run main.go
3 3 3

The above code does not output 1 2 3, but 3 3 3.
The correct approach should be to use &arr[i] instead of &v, as details in this kind of programming are very error-prone.

reason

The specific reasons are not complicated and can be explained in one sentence.

For arrays, slices, or strings, each iteration, the for-range statement passes a copy of the original value to the iteration variable, not the original value itself.

There is no evidence to say, but whether this is the case or not depends on the source code to speak.

The Go compiler will convert the for-range statement into a C-like three-stage loop structure, like this:

// Arrange to do a loop appropriate for the type.  We will produce
//   for INIT ; COND ; POST {
    
    
//           ITER_INIT
//           INDEX = INDEX_TEMP
//           VALUE = VALUE_TEMP // If there is a value
//           original statements
//   }

When iterating over an array, something like this:

// The loop we generate:
//   len_temp := len(range)
//   range_temp := range
//   for index_temp = 0; index_temp < len_temp; index_temp++ {
    
    
//           value_temp = range_temp[index_temp]
//           index = index_temp
//           value = value_temp
//           original body
//   }

slice:

//   for_temp := range
//   len_temp := len(for_temp)
//   for index_temp = 0; index_temp < len_temp; index_temp++ {
    
    
//           value_temp = for_temp[index_temp]
//           index = index_temp
//           value = value_temp
//           original body
//   }

From the above code snippet, two points can be summarized:

  • Before the loop starts, the array or slice is assigned to a new variable, and a copy occurs during the assignment, and what is iterated is actually a copy, which explains phenomenon 1.

  • During the loop, the iterated elements are assigned to a temporary variable, which is copied again. If the address is taken, it is the same every time, and it is the address of a temporary variable.

  • Reference article:

garbagecollected.org/2017/02/22/…
draveness.me/golang/docs…

recommended reading

Guess you like

Origin blog.csdn.net/u011397981/article/details/132010748