Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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言語におけるuintptruint64

  • 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言語のメモリ管理コードとの間のインターフェースをより自然で効率的なものにすることです。

  1. 型の一貫性: C言語では、メモリのサイズやポインタのオフセットを表現するためにsize_t型がよく用いられます。size_tは、uintptrと同様に、システムがアドレス空間を表現するのに十分な大きさの符号なし整数型です。malloc.Lookupuintptrを返すことで、Goの内部メモリ管理がCの慣習に沿うようになり、GoとCの間のFFI (Foreign Function Interface) を介したやり取りがよりスムーズになります。

  2. ポインタ算術との親和性: uintptrはポインタを整数として扱うための型であるため、メモリブロックのサイズをuintptrで表現することは、ポインタ算術(例: base + offset)を行う際に型変換が不要になるという利点があります。uint64でサイズを表現した場合、ポインタとの演算を行う前にuintptrへの明示的な型変換が必要になる可能性があり、これはコードの冗長性や潜在的なエラーの原因となり得ます。

  3. パフォーマンスと効率: 型変換の削減は、わずかながらもパフォーマンスの向上に寄与する可能性があります。また、コンパイラがより効率的なコードを生成しやすくなることも期待できます。

  4. テストコードへの影響: malloc.Lookupの戻り値の型が変更されたため、その関数を使用しているテストコードも同様にuint64からuintptrへの型変更を反映させる必要があります。これは、変更が単なるインターフェースの変更だけでなく、そのインターフェースを利用するコード全体に波及することを示しています。

この変更は、Go言語のランタイムが成熟していく過程で、低レベルのメモリ管理における型システムの厳密性と効率性を追求した結果と言えます。

コアとなるコードの変更箇所

このコミットでは、以下の2つのファイルが変更されています。

  1. src/lib/malloc.go
  2. 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関数の変更に合わせて、その戻り値の型を正しく扱うように修正されています。

  1. func OkAmount(size, n uint64) bool から func OkAmount(size, n uintptr) bool

    • OkAmount関数は、割り当てられたメモリのサイズが期待通りであるかをチェックするためのヘルパー関数です。
    • この関数の引数sizenも、malloc.Lookupの戻り値の型変更に合わせてuint64からuintptrに変更されました。これにより、型変換なしでmalloc.Lookupの戻り値を直接渡せるようになります。
  2. !OkAmount(uint64(size), n) から !OkAmount(uintptr(size), n) (2箇所)

    • AllocAndFree関数内でmalloc.Lookupが呼び出された後、その結果をOkAmount関数に渡す部分です。
    • malloc.Lookupから返されるn(サイズ)がuintptr型になったため、OkAmountに渡すsizeuintptrにキャストされるように変更されました。これにより、OkAmountの引数型と一致します。
  3. if stats.alloc != alloc - n から if stats.alloc != alloc - uint64(n)

    • この行は、メモリ解放後のstats.alloc(総割り当てメモリ量)が正しく更新されているかを検証する部分です。
    • nmalloc.Lookupから返されるサイズであり、このコミットによりuintptr型になりました。
    • stats.allocallocuint64型であるため、uintptr型のnuint64に明示的にキャストして減算を行う必要があります。これは、uintptruint64の型が異なるため、直接演算ができないためです。このキャストは、uintptrがポインタサイズに依存するのに対し、uint64は固定サイズであるという特性を考慮したものです。

これらの変更は、malloc.Lookupのインターフェース変更が、そのインターフェースを利用するコードにどのように影響するかを示す典型的な例です。型の一貫性を保つために、関連するすべてのコードが更新される必要があります。

関連リンク

参考にした情報源リンク