Preface
I believe that when you see this topic, you may think it is a clichéd menstrual topic. In fact, you can basically understand various situations by grasping a "value transfer", but recently I have encountered a deeper "small pit" and I will share it with you.
First of all, let’s start with the simplest one, look at the following code:
func main() { a := []int{7,8,9} ("len: %d cap:%d data:%+v\n", len(a), cap(a), a) ap(a) ("len: %d cap:%d data:%+v\n", len(a), cap(a), a) } func ap(a []int) { a = append(a, 10) }
What is the output of the above code?
I will not keep it aside and say it directly. After calling the a function to perform append operations, a is still []int{7,8,9}. The reason is very simple. There are no references in Go and it is all value passing. Value passing means that it is a copy of the data being passed. This sentence may be a little confused by novices, and the actual situation is quite strange, such as the following code:
func main() { a := []int{7,8,9} ("len: %d cap:%d data:%+v\n", len(a), cap(a), a) ap(a) ("len: %d cap:%d data:%+v\n", len(a), cap(a), a) } func ap(a []int) { a[0] = 1 a = append(a, 10) }
At this time, after ap, output a, you will see that a[0] becomes 1, but the cap of a is still 3, and it seems that 10 has not been appended?
This seems incredible. Isn’t it about value transfer? Why does it still affect the value of external variables? It is reasonable to say that either change or not change.
This is actually not incredible, because Go and C are different. Slice looks like an array, but is actually a structure. The data structure in the source code is:
type slice struct { array len int cap int }
This structure is actually easy to understand. The array is a real array pointer pointing to the head of a continuous memory space, and the len and cap represent length and capacity.
In other words, you seem to write ap(a []int) when passing arguments in the code. In fact, during the code compilation period, this code becomesap(a )
You can try to understand this, replace ap(a) withap(array: 0x123, len: 3, cap: 3)
. It can be clearly seen that the three parameters passed to the ap function are only 3 numerical values and do not establish any reference relationship with the external variable a. This is value transfer.
However, you may wonder, why will I change the value of a[0] be reflected outside? In fact, you should have already figured it out by yourself after seeing this, because array is an address value (such as 0x123). This address is passed into the ap function, but the address 0x123 it represents and the 0x123 external a is a memory address. At this time, when you modify a[0], you are actually modifying the value stored in the 0x123 address, so the outside will of course be affected.
For example, suppose you are a train station cargo administrator, and you are managing the loading and unloading of cargoes from the first to the third carriages (carriages are interoperable). One day you are sick, find someone (called A) to take over temporarily. But train goods cannot be touched by anyone who wants to, you have to have proof. So you copied the original certificate in your hand to A, and at the same time gave A the key to the first car. Because they happened to be busy in those days, the webmaster asked A to be responsible for the fourth car, so A also obtained the original certificate of carriage 4. After a while, when you come back from illness, you still only have 1 to 3 carriages ID cards. You can see what A has done recently in 1 to 3 carriages, but you are not qualified to go to 4 carriages.
The above example should explain the scene of slice parameter transfer. Remember, there are only value transfers in Go.
Is it over? However, things are not that simple. I've encountered this problem recently when I was working. According to the example above, although you are not qualified to view the 4th car, if you are curious, you can peek, because they are continuous and interoperable, just as an array is also a continuous piece of memory, so there is such a code:
func main() { a := []int{} a = append(a, 7,8,9) ("len: %d cap:%d data:%+v\n", len(a), cap(a), a) ap(a) ("len: %d cap:%d data:%+v\n", len(a), cap(a), a) p := (&a[2]) q := uintptr(p)+8 t := (*int)((q)) (*t) } func ap(a []int) { a = append(a, 10) }
Although the external cap and len have not changed, the ap function has appended a 10 to the same memory address. Can I peek at it using a trick method? For example, finda[2]
The address, if you move the length of an int in the future, it should be the 10 new addition to the ap function, right? It should be noted here that the server of Go official website is 32-bit, so when the go playground executes this code, int is 4 bytes.
The execution result was the same as I expected!
But problems follow
func main() { a := []int{7,8,9} ("len: %d cap:%d data:%+v\n", len(a), cap(a), a) ap(a) ("len: %d cap:%d data:%+v\n", len(a), cap(a), a) p := (&a[2]) q := uintptr(p)+8 t := (*int)((q)) (*t) } func ap(a []int) { a = append(a, 10) }
The only difference between this and the above example is that slice is used in the beginning[]int{7,8,9}
Initialize this way. The execution result *t is 3 instead of 10, which is more confusing. Why? Isn't it a continuous memory space?
The problem involved here is the growth problem of slice. When you append, you find that the cap is not enough, and the space will be reassigned. For the specific source code, please refer to the growthslice function in runtime/. I won't talk about too much details here, I will only talk about the results. When a growslice occurs, a larger memory will be allocated to the slice, and then the original data will be copied over and the slice's array pointer will be pointed to the new memory. That is to say, if the previous data is stored in the memory address 0x0 0x8 0x10, when the growslice does not occur, the value of the new append will be stored to 0x18. However, when the growslice occurs, all the previous data will be copied to the new address 0x1000 0x1008 0x1010, and the value of the new append will be placed to 0x1018.
At this time, you can understand why sometimes you can get data with unsafe, but sometimes you can't get it. Maybe you can understand why this package is called unsafe. But unsafe is not really unsafe, it means that it is very easy to unsafe if you use the wrong posture. But if the posture is elegant, it is actually very safe. For slice operations, if you want to use unsafe, remember to pay attention to whether the cap sends changes, it means memory migration
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.