SIMPLE

Go言語のSliceとappend()時の再allocationの話

Goで配列構造で扱うためのsliceは、データの実態であるArrayの特定範囲を参照するものです。つまり、0からsliceを作成すると、黙示的に裏Arrayが作成される仕組みです。

また、既存のsliceから抜粋したsliceを作成することができます。新たなsliceは、もとのsliceと同じ実態データを指しています。
RustのsliceやC#のSpan<T>似たようなものですね。

この機能を使う利点としては、部分参照をしたい際に余計なコピーが発生せず、参照先の実態データが共通なので更新操作が1回で済む点ですね。

ただし、注意点があります。
append()insert()などして要素数がCapacityを超えると、配列の内容がまるまるコピーされて再アロケートされます。
本来であれば、これは配列の仕組みを抽象化してくれている親切な機能なのですが、気をつけないとバグを生みます。

具体的にどうなるかというと、親sliceが指している実態のarrayと、子sliceが指している実態のarrayが別物になります。

このことを意識していないと、知らず知らずのうちにバグができてしまいますね。

以下は、再アロケーションの挙動を確認するコードです。
2回append()してキャパオーバーした再、originalsliceのアドレスが変わっています。

package main

import (
    "fmt"
)

func main() {
    original := make([]int, 2, 3)  // { 0, 0, (null) }
    branched := original[0:1]        // originalの0~1を抜粋したsliceを作成

    original = append(original, 333) // { 0, 0, 333 }

    fmt.Printf("original: %v\n", &original [0])
    fmt.Printf("branched: %v\n", &branched[0])

    original = append(original, 444) // { 0, 0, 0, 444 }   // 再アロケーション発生

    fmt.Println()
    fmt.Printf("original: %v\n", &original[0])
    fmt.Printf("branched: %v\n", &branched[0])
}
original: 0xc0000160f0
branched: 0xc0000160f0

original: 0xc000072180
branched: 0xc0000160f0

nafell

大学生、26卒エンジニア志望。アプリ開発サークルを設立し、lounas.jpのバックエンド・DB設計を行いました。2年後期にインターンで設計の手法(要件定義~詳細設計)を学び、IoTシステムの調査・開発を行いました。