[インデックス 1515] ファイルの概要
このコミットは、Go言語のメモリ管理ライブラリであるmalloc
パッケージ内のLookup
関数の戻り値の型をuint64
からuintptr
に変更するものです。これにより、GoコードがCコードと一貫性を持つように修正され、メモリサイズ表現の整合性が向上します。
コミット
-
Author: Ian Lance Taylor iant@golang.org
-
Date: Fri Jan 16 15:03:22 2009 -0800
-
Commit Message: Change malloc.Lookup to return the size as uintptr rather than uint64. This changes the Go code to be consistent with the C code.
R=rsc DELTA=6 (0 added, 0 deleted, 6 changed) OCL=22983 CL=22987
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/03c40f5122eeb9b9abc4492c043af9e033c1503a
元コミット内容
Change malloc.Lookup to return the size as uintptr rather than
uint64. This changes the Go code to be consistent with the C
code.
R=rsc
DELTA=6 (0 added, 0 deleted, 6 changed)
OCL=22983
CL=22987
変更の背景
この変更の主な背景は、Go言語のメモリ管理部分と、それが連携する可能性のあるC言語のコードベースとの間で、メモリサイズを表す型の整合性を確保することにあります。
Go言語の初期段階において、malloc.Lookup
関数は特定のメモリブロックのアドレスとサイズを返す役割を担っていました。しかし、そのサイズをuint64
型で返していたため、C言語の文脈でポインタのオフセットやサイズを表す際に一般的に使用されるuintptr
型との間に不一致が生じていました。
uintptr
型は、ポインタを保持するのに十分な大きさの符号なし整数型であり、システムアーキテクチャ(32ビットまたは64ビット)に応じてサイズが変化します。一方、uint64
は常に64ビットの符号なし整数です。メモリサイズやアドレスオフセットを扱う場合、uintptr
を使用することで、基盤となるシステムのポインタサイズに合わせた、より自然で効率的な表現が可能になります。
この不一致は、特にGoのランタイムがC言語で書かれた部分と相互作用する際に、型変換のオーバーヘッドや潜在的なバグの原因となる可能性がありました。このコミットは、このような不整合を解消し、Goのメモリ管理コードがCコードとよりシームレスに連携できるようにすることを目的としています。
前提知識の解説
Go言語におけるuintptr
とuint64
-
uintptr
:uintptr
は、ポインタを保持するのに十分な大きさの符号なし整数型です。- そのサイズは、実行されているシステムのポインタサイズ(例: 32ビットシステムでは32ビット、64ビットシステムでは64ビット)に依存します。
- 主に、Goのガベージコレクタや低レベルのメモリ操作など、ポインタと整数表現の間で変換が必要な場面で使用されます。
unsafe
パッケージと組み合わせて使用されることが多く、Goの型安全性を一時的にバイパスしてメモリを直接操作する際に利用されます。uintptr
は算術演算が可能ですが、Goの型システムはuintptr
をポインタとして扱わないため、誤ったポインタ操作によるメモリ破壊のリスクがあります。
-
uint64
:uint64
は、常に64ビット(8バイト)の符号なし整数型です。- そのサイズはシステムアーキテクチャに依存せず、常に一定です。
- 主に、大きな数値、ID、または固定サイズのデータ表現に使用されます。
- メモリサイズを表すことも可能ですが、ポインタのオフセットやアドレスを直接扱う場合には
uintptr
の方がより適切です。
Goのメモリ管理とmalloc
パッケージ
Go言語は独自のランタイムとガベージコレクタを持っており、メモリの割り当てと解放を自動的に管理します。malloc
パッケージ(このコミットの時点ではsrc/lib/malloc.go
に存在)は、Goのランタイムが内部的に使用する低レベルのメモリ割り当てメカニズムの一部です。
malloc.Alloc(size uint64) *byte
: 指定されたサイズのメモリブロックを割り当て、その先頭へのポインタを返します。malloc.Free(*byte)
: 割り当てられたメモリブロックを解放します。malloc.Lookup(*byte) (*byte, size)
: 特定のポインタが指すメモリブロックの先頭アドレスとサイズを返します。このコミットの変更対象となる関数です。
これらの関数は、Goのユーザーが直接呼び出すことは通常なく、Goランタイムの内部でヒープメモリの管理のために使用されます。
技術的詳細
このコミットの技術的な核心は、malloc.Lookup
関数の戻り値の型をuint64
からuintptr
に変更することによって、Goのメモリ管理コードとC言語のメモリ管理コードとの間のインターフェースをより自然で効率的なものにすることです。
-
型の一貫性: C言語では、メモリのサイズやポインタのオフセットを表現するために
size_t
型がよく用いられます。size_t
は、uintptr
と同様に、システムがアドレス空間を表現するのに十分な大きさの符号なし整数型です。malloc.Lookup
がuintptr
を返すことで、Goの内部メモリ管理がCの慣習に沿うようになり、GoとCの間のFFI (Foreign Function Interface) を介したやり取りがよりスムーズになります。 -
ポインタ算術との親和性:
uintptr
はポインタを整数として扱うための型であるため、メモリブロックのサイズをuintptr
で表現することは、ポインタ算術(例:base + offset
)を行う際に型変換が不要になるという利点があります。uint64
でサイズを表現した場合、ポインタとの演算を行う前にuintptr
への明示的な型変換が必要になる可能性があり、これはコードの冗長性や潜在的なエラーの原因となり得ます。 -
パフォーマンスと効率: 型変換の削減は、わずかながらもパフォーマンスの向上に寄与する可能性があります。また、コンパイラがより効率的なコードを生成しやすくなることも期待できます。
-
テストコードへの影響:
malloc.Lookup
の戻り値の型が変更されたため、その関数を使用しているテストコードも同様にuint64
からuintptr
への型変更を反映させる必要があります。これは、変更が単なるインターフェースの変更だけでなく、そのインターフェースを利用するコード全体に波及することを示しています。
この変更は、Go言語のランタイムが成熟していく過程で、低レベルのメモリ管理における型システムの厳密性と効率性を追求した結果と言えます。
コアとなるコードの変更箇所
このコミットでは、以下の2つのファイルが変更されています。
src/lib/malloc.go
test/mallocrep1.go
src/lib/malloc.go
の変更
--- a/src/lib/malloc.go
+++ b/src/lib/malloc.go
@@ -16,4 +16,4 @@ export type Stats struct {
export func Alloc(uint64) *byte;
export func Free(*byte);
export func GetStats() *Stats;
-export func Lookup(*byte) (*byte, uint64);
+export func Lookup(*byte) (*byte, uintptr);
test/mallocrep1.go
の変更
--- a/test/mallocrep1.go
+++ b/test/mallocrep1.go
@@ -22,7 +22,7 @@ var longtest = flag.Bool("l", false, "long test");
var b []*byte;
var stats = malloc.GetStats();
-func OkAmount(size, n uint64) bool {
+func OkAmount(size, n uintptr) bool {
if n < size {
return false
}
@@ -46,7 +46,7 @@ func AllocAndFree(size, count int) {
for i := 0; i < count; i++ {
b[i] = malloc.Alloc(uint64(size));
base, n := malloc.Lookup(b[i]);
-\t\tif base != b[i] || !OkAmount(uint64(size), n) {
+\t\tif base != b[i] || !OkAmount(uintptr(size), n) {
\tpanicln("lookup failed: got", base, n, "for", b[i]);
}
if malloc.GetStats().sys > 1e9 {
@@ -65,12 +65,12 @@ func AllocAndFree(size, count int) {
}
alloc := stats.alloc;
base, n := malloc.Lookup(b[i]);
-\t\tif base != b[i] || !OkAmount(uint64(size), n) {
+\t\tif base != b[i] || !OkAmount(uintptr(size), n) {
\tpanicln("lookup failed: got", base, n, "for", b[i]);
}
malloc.Free(b[i]);
-\t\tif stats.alloc != alloc - n {
-\t\t\tpanicln("free alloc got", stats.alloc, "expected", alloc - n, "after free of", n);\n+\t\tif stats.alloc != alloc - uint64(n) {
+\t\t\tpanicln("free alloc got", stats.alloc, "expected", alloc - uint64(n), "after free of", n);\n \t\t}\n if malloc.GetStats().sys > 1e9 {
\tpanicln("too much memory allocated");
コアとなるコードの解説
src/lib/malloc.go
の変更点
export func Lookup(*byte) (*byte, uint64);
からexport func Lookup(*byte) (*byte, uintptr);
へ- これは、
malloc.Lookup
関数のシグネチャ(関数宣言)の変更です。 - 以前は、第2の戻り値(メモリブロックのサイズ)が
uint64
型でした。 - この変更により、サイズが
uintptr
型で返されるようになります。 - この変更が、Goのメモリ管理の低レベル部分における型の一貫性をC言語の慣習に合わせるための主要な変更点です。
- これは、
test/mallocrep1.go
の変更点
このファイルはmalloc
パッケージのテストコードであり、malloc.Lookup
関数の変更に合わせて、その戻り値の型を正しく扱うように修正されています。
-
func OkAmount(size, n uint64) bool
からfunc OkAmount(size, n uintptr) bool
へOkAmount
関数は、割り当てられたメモリのサイズが期待通りであるかをチェックするためのヘルパー関数です。- この関数の引数
size
とn
も、malloc.Lookup
の戻り値の型変更に合わせてuint64
からuintptr
に変更されました。これにより、型変換なしでmalloc.Lookup
の戻り値を直接渡せるようになります。
-
!OkAmount(uint64(size), n)
から!OkAmount(uintptr(size), n)
へ (2箇所)AllocAndFree
関数内でmalloc.Lookup
が呼び出された後、その結果をOkAmount
関数に渡す部分です。malloc.Lookup
から返されるn
(サイズ)がuintptr
型になったため、OkAmount
に渡すsize
もuintptr
にキャストされるように変更されました。これにより、OkAmount
の引数型と一致します。
-
if stats.alloc != alloc - n
からif stats.alloc != alloc - uint64(n)
へ- この行は、メモリ解放後の
stats.alloc
(総割り当てメモリ量)が正しく更新されているかを検証する部分です。 n
はmalloc.Lookup
から返されるサイズであり、このコミットによりuintptr
型になりました。stats.alloc
とalloc
はuint64
型であるため、uintptr
型のn
をuint64
に明示的にキャストして減算を行う必要があります。これは、uintptr
とuint64
の型が異なるため、直接演算ができないためです。このキャストは、uintptr
がポインタサイズに依存するのに対し、uint64
は固定サイズであるという特性を考慮したものです。
- この行は、メモリ解放後の
これらの変更は、malloc.Lookup
のインターフェース変更が、そのインターフェースを利用するコードにどのように影響するかを示す典型的な例です。型の一貫性を保つために、関連するすべてのコードが更新される必要があります。
関連リンク
- Go言語の
unsafe
パッケージに関するドキュメント: https://pkg.go.dev/unsafe (uintptrの利用例が示されています) - Go言語の基本型に関するドキュメント: https://go.dev/ref/spec#Numeric_types
参考にした情報源リンク
- Go言語の
uintptr
とuint64
の違いに関する一般的な解説記事 (Web検索結果に基づく): - Go言語のメモリ管理に関する一般的な情報 (Web検索結果に基づく):
- https://go.dev/doc/effective_go#allocation_new
- https://go.dev/doc/effective_go#allocation_make
- (注:
malloc
パッケージはGoランタイムの内部実装であり、直接的な公式ドキュメントは少ないです。)