What's going on while using "append" in memory?

271 views
Skip to first unread message

Miraddo

unread,
Aug 6, 2021, 12:43:44 PM8/6/21
to golang-nuts
Hello all, 

I can not understand why when we use append, it does not rewrite values in the same address in the memory that it had before, 

(I'm not sure it "append" problem or the variable |  or just my problem for sure :) ) 

let me explain what I mean, 

I solve a question in LeetCode (189. Rotate Array). The question said we give you an array, and you should rotate the array to the right by k steps, where k is non-negative.

I thought, so it's easy, we can do something like this :

```
func rotate(nums []int, k int) {
       nums = append(nums[len(nums) - k:], nums[:len(nums) - k]...)
}
```
When I used `fmt.Println` I got the right value; it was fine, but while running the test case, I got the same value as before, and it says so you didn't change anything there is still the same value as before, and it was completely wrong, 

after that, I rechecked my code; I did something like this :

```
func rotate(nums []int, k int) {

      for i := range nums{
            fmt.Println(&nums[i], nums[i])
      }

     nums = append(nums[len(nums) - k:], nums[:len(nums) - k]...)

     fmt.Println("-----------------------------------------")
     for i := range nums{
           fmt.Println(&nums[i], nums[i])
      }
}
```
the result was like this :

Screenshot 2021-08-06 115209.png

I saw all addresses in memory was changed, 

now my question is why it doesn't rewrite the values in the same address as before?

Could you please describe to me what's going on in memory, why it happened, and why it couldn't rewrite values in the same location in the memory? 

I don't have strong knowledge of what's going on in memory in Golang or at all. If there are any resources out there, could you please share them with me?

Thanks again, 

Konstantin Khomoutov

unread,
Aug 6, 2021, 1:00:35 PM8/6/21
to Miraddo, golang-nuts
On Fri, Aug 06, 2021 at 12:42:34AM -0700, Miraddo wrote:

Hi!

> I can not understand why when we use append, it does not rewrite values in
> the same address in the memory that it had before,
>
> (I'm not sure it "append" problem or the variable | or just my problem for
> sure :) )
[...]
> I saw all addresses in memory was changed,
>
> now my question is why it doesn't rewrite the values in the same address as
> before?
[...]

Have you read and understood [1] and [2]?
I think the information from these articles will enable you to solve your
problem all by yourself.

I would also argue that rotating an array *in place* by a non-negative value
is "classically" solved by using a temporary variable and moving of the
array elements; the latter can be done by using copy().

1. https://blog.golang.org/slices-intro
2. https://blog.golang.org/slices

Henry

unread,
Aug 8, 2021, 11:55:23 AM8/8/21
to golang-nuts
Append is for appending items at the end of a slice. If there is enough space, Go will simply append the items and return the slice. If there is not enough space, Go will create a new slice with double the capacity of the existing slice or at least enough space to hold the new items (whichever is larger), copy the items, and return the new slice.

In your case, when you first instantiate the slice, Go allocates just enough memory to hold the numbers. Then, in your append, Go checks whether "nums[len(nums) - k:]" has enough remaining capacity to hold the new items. Note that "nums[len(nums) - k:]" is a new slice and it has a shorter capacity than the original slice. Since there is not enough space, Go creates a new slice, copy the items, and returns the new slice. That is how you got the new slice.

If what you need is rotation, you may want to rotate the items manually.

Brian Candler

unread,
Aug 8, 2021, 12:06:27 PM8/8/21
to golang-nuts
On Friday, 6 August 2021 at 18:00:35 UTC+1 Konstantin Khomoutov wrote:
Have you read and understood [1] and [2]?
...

Also possibly of interest (although it doesn't mention rotation):

Miraddo

unread,
Aug 9, 2021, 1:06:14 AM8/9/21
to golang-nuts
Thank you, Konstantin. I really appreciate your response, I didn't read those links before you sent them to me, but I read them yesterday, now everything is so clear to me, and I know what is going on with "slice," "append," and "copy" in memory.
I was so confused, but now I know what's going on.

Miraddo

unread,
Aug 9, 2021, 1:22:27 AM8/9/21
to golang-nuts
Henry Thanks for your time,
You're right. Sounds reasonable. But you know, the first time I saw this thing, I was like, when I pass the new slice to my existing slice, does it not replace the item in the same address in memory that my slice has before? 
Because when I pass new data to my variable, it means I didn't need that data anymore. I need the new data, so when the length of array/slice is the same, Golang has not pasted the data into the same address in memory that my variable has before. It just changed the variable's pointer to a new address that "append" created in the memory.

Miraddo

unread,
Aug 9, 2021, 1:25:41 AM8/9/21
to golang-nuts
Thanks, Brain. It is absolutely fantastic. I saw this SliceTricks two days ago. Nice to mention it.

Henry

unread,
Aug 10, 2021, 1:25:47 AM8/10/21
to golang-nuts
Just sharing some tips.

When working with slices, it is often a good idea to lend a helping hand to the compiler.

Don't declare something like this, unless you have no other choices.
```
var slice []int
```
If you know the data right away, you can do something like this:
```
slice := []int{1,2,3,4,5}
```
If you don't know the data, but you know the size, you can do something like this:
```
slice:=make([]int, 5)

//example: working with slice
for a:=0; a<5; a++ {
   slice[a] = a+1
}
```
If you don't know the data and the size, you can do something like this:
```
slice:=make([]int, 0, 5)
//make a slice with 0 length, but allocate memory for 5 items (capacity of 5).
//The capacity can be of any arbitrary length. Think of it like a suggestion of what you think the data size may be.

//example
for a:=0; a<10; a++ {
   slice = append(slice, a+1) 
   //As long as slice length does not exceed its capacity, no new slice is allocated.
   //If the length exceed the capacity, the compiler will allocate twice the original capacity or at least enough to store the items (whichever is larger).
   //In this case, asuming only one item is appended at a time, append will create new slices with the following capacity: 5, 10, 20, 40, ...
)
```
Now let's see what happens when you do this:
```
var slice []int

for a:=0; a<10; a++ {
   slice = append(slice, a+1)
   //When a=0, the slice has 0 capacity but need 1, hence a new slice is created with 1 capacity and 1 length.
   //When a=1, the slice has 1 capacity but need 2, hence a new slice is created with 2 capacity (twice the original capacity: 2x1) and 2 length.
   //When a=2, the slice has 2 capacity but need 3, hence a new slice is created with 4 capacity (twice the original capacity: 2x2) and 3 length.
   //When a=3, the slice has 4 capacity and need 4, hence no new slice is created and the existing slice is returned with 4 length.
   //When a=4, the slice has 4 capacity but need 5, hence a new slice is created with 8 capacity (twice the original capacity: 2x4) and 5 length.
   //When a=5, the slice has 8 capacity and need 6, hence no new slice is created and the existing slice is returned with 6 length.
   //etc.
}
```
If you do not specify the length or the capacity, you are bound to create several unnecessary memory allocations. It may not matter much in a real world application, but I think it is a good idea to help out the compiler as long as it is reasonable to do so, and specifying the length or the capacity does not take much effort on your part.

Marvin Renich

unread,
Aug 10, 2021, 8:04:55 AM8/10/21
to golan...@googlegroups.com
* Henry <henry.ad...@gmail.com> [210810 01:26]:
> Just sharing some tips.
>
> When working with slices, it is often a good idea to lend a helping hand to
> the compiler.
>
> Don't declare something like this, unless you have no other choices.
> ```
> var slice []int
> ```

I believe this is bad advice.

First, if «slice» is to be used to hold a view into a backing array
generated elsewhere, e.g. the result of another function, attempting to
pre-allocate a backing array that will never be used is wasteful.

If you are using «slice» to build a result, and you have a good idea
what the final size of the result will be, preallocating something a bit
larger than the typical result indeed can be a good idea.

If you know for sure exactly what the initial size is going to be, than
preallocating with make() won't hurt, but doesn't do any better than
letting the first append do the allocation.

However, trying to _guess_ the initial size is often a waste of an
allocation.

var slice []int

allocates the three-word slice header, but does not allocate any backing
array. The first append will allocate a backing array of the correct
size.

Suppose you have a good idea that the initial size will be 5, so you
write

var slice = make([]int, 0, 5)

But than it turns out that the initial size is really 6:

slice = append(slice, data[i:j]...)

where j-i is 6, now the initial allocation of 5 elements was wasted.

If you are appending single elements in a loop, as in your simple
examples, than your advice is somewhat more sound. In those cases, the
"initial" size is always 1, but the "final" size is its size at the end
of the loop, so making an initial estimate makes a lot more sense.

The take-away is that slice variables are used in many different ways,
and your advice holds in specific use cases, such as building a result
by appending one element at a time. It does not hold in many real, more
sophisticated, use cases.

...Marvin

Reply all
Reply to author
Forward
0 new messages