web-dev-qa-db-ja.com

Goが(&)マップメンバーのアドレスの取得を禁止しているのに、(&)スライス要素を許可しているのはなぜですか?

Goでは、マップメンバーのアドレスを取得できません。

// if I do this:
p := &mm["abc"]
// Syntax Error - cannot take the address of mm["abc"]

理論的根拠は、Goがこのアドレスの取得を許可している場合、マップバックストアが拡大または縮小すると、アドレスが無効になり、ユーザーを混乱させる可能性があるためです。

ただし、Goスライスは容量を超えると再配置されますが、Goではスライス要素のアドレスを取得できます。

 a := make([]Test, 5)
 a[0] = Test{1, "dsfds"}
 a[1] = Test{2, "sdfd"}
 a[2] = Test{3, "dsf"}

 addr1 := reflect.ValueOf(&a[2]).Pointer()
 fmt.Println("Address of a[2]: ", addr1)

 a = append(a, Test{4, "ssdf"})
 addrx := reflect.ValueOf(&a[2]).Pointer()
 fmt.Println("Address of a[2] After Append:", addrx)

 // Note after append, the first address is invalid
 Address of a[2]:  833358258224
 Address of a[2] After Append: 833358266416

Goがこのように設計されているのはなぜですか?スライス要素のアドレスを取ることの特別な点は何ですか?

20
NeoWang

スライスとマップには大きな違いがあります。スライスはバッキング配列によってバッキングされますが、マップはバッキングされません。

マップが拡大または縮小すると、マップ要素への潜在的なポインタが、どこも指し示していないダングリングポインタ(初期化されていないメモリ)になる可能性があります。ここでの問題は「ユーザーの混乱」ではなく、Goの主要な設計要素であるダングリングポインターがないことです。

スライスの容量が不足すると、新しい、より大きなバッキングアレイが作成され、古いバッキングアレイが新しいバッキングアレイにコピーされます。および古いバッキング配列残り既存。したがって、古いバッキング配列を指す「未成長」スライスから取得されたポインタは、引き続き有効なメモリへの有効なポインタです。

古いバッキングアレイを指しているスライスがある場合(たとえば、スライスを容量を超えて拡張する前にスライスのコピーを作成したため)、古いバッキングアレイにアクセスします。これはスライス要素のポインタとはあまり関係ありませんが、スライスは配列へのビューであり、配列はスライスの拡張中にコピーされます。

スライスの縮小中に「スライスのバッキング配列を減らす」ことはないことに注意してください。

19
Volker

マップとスライスの基本的な違いは、マップは動的なデータ構造であり、マップに含まれる値が大きくなるにつれて移動することです。 Goマップの特定の実装は、すべての値がより大きなメモリ構造に移動されるまで、挿入および削除操作中に少しずつ大きくなる場合があります。そのため、値を削除すると、突然別の値が移動する可能性があります。一方、スライスは、サブアレイへの単なるインターフェイス/ポインタです。スライスは決して成長しません。追加関数は、スライスをより容量の大きい別のスライスにコピーする場合がありますが、古いスライスはそのまま残し、単なるインデックス演算子ではなく関数でもあります。

マップ実装者自身の言葉で:

https://www.youtube.com/watch?v=Tl7mi9QmLns&feature=youtu.be&t=21m45s "これはこの成長手順を妨げるので、バケット内のエントリのアドレスを取得すると、それから私はそのエントリを長い間保持し、その間にマップが大きくなり、突然そのポインタが新しいバケットではなく古いバケットを指し、そのポインタが無効になったため、次の機能を提供するのは困難です成長の仕組みを制限することなく、マップ内の値のアドレスを取得します... C++は異なる方法で成長するため、バケットのアドレスを取得できます。」

したがって、&m [x]が許可されていて、短期間の操作(値を変更してからそのポインターを再度使用しない)に役立つ場合でも、実際にはマップが内部的にそれを行いますが、言語は設計者/実装者は、マップを安全に使用することを選択しました。プログラマーが考えていたものとは異なるデータを指すことに気付かずにポインターを長時間保持する可能性のあるプログラムの微妙なバグを回避するために、&m [x]を許可しません。

関連するコメントについては、 Goがマップ値のアドレスの取得を許可しないのはなぜですか? も参照してください。

2
user13097