[インデックス 11450] ファイルの概要
本コミットは、Go言語のツールチェインに含まれるリンカ群(5l
, 6l
, 8l
, ld
)におけるメモリリークを修正するものです。具体的には、動的に確保された文字列バッファが使用後に解放されていなかった問題を解決し、これらのツールが実行される際のメモリ効率を改善します。
コミット
commit 916eea04f8109a9ef8383341d00b884a9d687399
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sun Jan 29 12:46:26 2012 -0500
5l, 6l, 8l, ld: remove memory leaks
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5569085
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/916eea04f8109a9ef8383341d00b884a9d687399
元コミット内容
diff --git a/src/cmd/5l/asm.c b/src/cmd/5l/asm.c
index fe3a2f3f2a..df33fbe251 100644
--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -91,6 +91,7 @@ needlib(char *name)
/* reuse hash code in symbol table */
p = smprint(".dynlib.%s", name);
s = lookup(p, 0);
+ free(p);
if(s->type == 0) {
s->type = 100; // avoid SDATA, etc.
return 1;
diff --git a/src/cmd/6l/asm.c b/src/cmd/6l/asm.c
index 7d36b170eb..121aa8613e 100644
--- a/src/cmd/6l/asm.c
+++ b/src/cmd/6l/asm.c
@@ -112,6 +112,7 @@ needlib(char *name)
/* reuse hash code in symbol table */
p = smprint(".elfload.%s", name);
s = lookup(p, 0);
+ free(p);
if(s->type == 0) {
s->type = 100; // avoid SDATA, etc.
return 1;
diff --git a/src/cmd/8l/asm.c b/src/cmd/8l/asm.c
index 0fe4cf1123..dcaa0b192b 100644
--- a/src/cmd/8l/asm.c
+++ b/src/cmd/8l/asm.c
@@ -108,6 +108,7 @@ needlib(char *name)
/* reuse hash code in symbol table */
p = smprint(".dynlib.%s", name);
s = lookup(p, 0);
+ free(p);
if(s->type == 0) {
s->type = 100; // avoid SDATA, etc.
return 1;
diff --git a/src/cmd/ld/go.c b/src/cmd/ld/go.c
index 2bda628cde..3271be1f59 100644
--- a/src/cmd/ld/go.c
+++ b/src/cmd/ld/go.c
@@ -482,6 +482,7 @@ loaddynimport(char *file, char *pkg, char *p, int n)
if(q)
*q++ = '\0';
s = lookup(name, 0);
+ free(name);
if(s->type == 0 || s->type == SXREF) {
s->dynimplib = lib;
s->dynimpname = def;
変更の背景
Go言語の初期のツールチェイン、特にリンカ(5l
, 6l
, 8l
, ld
)はC言語で実装されていました。C言語では、動的にメモリを確保した場合、そのメモリはプログラマが明示的に解放する責任があります。本コミット以前のコードでは、特定の文字列操作関数(smprint
など)によって動的に確保されたメモリが、その使用後に適切に解放されていませんでした。
これにより、リンカが実行されるたびに、少量のメモリがシステムに返却されずに残り続ける「メモリリーク」が発生していました。リンカはビルドプロセスにおいて頻繁に実行されるツールであるため、このメモリリークはビルドシステムの安定性や、長時間稼働する環境でのリソース枯渇に繋がる可能性がありました。このコミットは、これらの潜在的な問題を解消し、ツールチェインの堅牢性と効率性を向上させることを目的としています。
前提知識の解説
Go言語のツールチェインとリンカ
Go言語のビルドシステムは、ソースコードを最終的な実行可能ファイルに変換するために複数のツールを使用します。その中でも、本コミットで言及されている5l
, 6l
, 8l
, ld
は、Goの初期のリンカ群を指します。
5l
: ARMアーキテクチャ向けのリンカ。6l
: x86-64 (AMD64) アーキテクチャ向けのリンカ。8l
: x86 (32-bit) アーキテクチャ向けのリンカ。ld
: 一般的なリンカの総称、またはGoツールチェインにおける主要なリンカプロセスを指すこともあります。
これらのリンカは、コンパイルされたオブジェクトファイルやライブラリを結合し、実行可能なバイナリを生成する役割を担っています。
C言語におけるメモリ管理 (malloc
とfree
)
C言語では、プログラムの実行中に動的にメモリを確保・解放するための関数が標準ライブラリで提供されています。
malloc()
(または関連関数calloc()
,realloc()
): ヒープ領域から指定されたサイズのメモリブロックを確保し、そのブロックの先頭へのポインタを返します。メモリが確保できない場合はNULLを返します。free()
:malloc
などによって確保されたメモリブロックを解放し、そのメモリを再利用可能にします。free
を呼び出さないと、確保されたメモリはプログラムが終了するまでシステムに返却されず、メモリリークの原因となります。
メモリリーク
メモリリークとは、プログラムが動的に確保したメモリを使い終わった後も解放せず、そのメモリへの参照(ポインタ)も失ってしまうことで、そのメモリが二度とアクセスできなくなり、かつ解放もされない状態になることを指します。これにより、利用可能なメモリが徐々に減少し、最終的にはシステム全体のパフォーマンス低下や、他のプログラムのクラッシュを引き起こす可能性があります。
smprint
関数
GoのツールチェインのC言語コードベースで頻繁に見られるsmprint
関数は、標準Cライブラリのsprintf
に似た機能を提供しますが、その特徴は結果の文字列を動的にメモリ確保して返す点にあります。つまり、smprint
が返すポインタは、malloc
などによって確保されたメモリを指しており、使用後にはfree
で明示的に解放する必要があります。
lookup
関数
lookup
関数は、与えられた文字列(シンボル名など)を内部のシンボルテーブルで検索する関数です。この関数は、引数として渡された文字列のメモリの所有権を引き継がない(つまり、lookup
関数内でそのメモリを解放しない)のが一般的です。そのため、lookup
に渡すためにsmprint
などで確保したメモリは、lookup
の呼び出し後に呼び出し元で解放する必要があります。
技術的詳細
本コミットの技術的詳細は、C言語におけるメモリ管理の基本原則に忠実に従うことで、メモリリークを解消した点にあります。
問題の箇所では、smprint
関数が.dynlib.%s
や.elfload.%s
といった形式の文字列を動的に生成し、その結果をポインタp
(またはname
)に格納していました。このポインタp
(またはname
)は、その後lookup(p, 0)
(またはlookup(name, 0)
)という形でシンボルテーブルの検索に使用されていました。
しかし、lookup
関数は渡された文字列のメモリを解放する責任を持たないため、smprint
によって確保されたメモリは、lookup
の呼び出し後も解放されずに残っていました。これがメモリリークの原因です。
このコミットでは、lookup
関数の呼び出し直後にfree(p);
(またはfree(name);
)という行を追加することで、この問題を解決しています。これにより、動的に確保された文字列バッファが、その役割を終えた直後にシステムに返却されるようになり、メモリリークが防止されます。
この修正は、Goツールチェインの安定性とリソース効率を向上させる上で重要です。特に、リンカのような頻繁に実行されるツールでは、小さなメモリリークでも累積すると大きな問題に発展する可能性があるため、このような修正はシステムの健全性を保つ上で不可欠です。
コアとなるコードの変更箇所
変更は、Goツールチェインのリンカを構成する以下の4つのファイルにわたっています。
src/cmd/5l/asm.c
src/cmd/6l/asm.c
src/cmd/8l/asm.c
src/cmd/ld/go.c
それぞれのファイルにおいて、smprint
関数(またはそれに相当するメモリ確保を伴う文字列生成)によって返されたポインタが、lookup
関数に渡された直後にfree()
されるように修正されています。
具体的には、以下のパターンでfree
が追加されています。
p = smprint("...", name); // 動的にメモリ確保
s = lookup(p, 0); // 確保したメモリを使用
free(p); // 使用後にメモリを解放
src/cmd/ld/go.c
では、name
という変数に対して同様のパターンでfree(name);
が追加されています。
コアとなるコードの解説
各ファイルの変更は非常にシンプルで、free()
関数の追加のみです。
-
src/cmd/5l/asm.c
、src/cmd/6l/asm.c
、src/cmd/8l/asm.c
のneedlib
関数内:smprint
によって生成された文字列p
がlookup
に渡された後、free(p);
が追加されています。これは、smprint
が動的にメモリを確保するため、そのメモリが不要になった時点で解放する必要があるためです。 -
src/cmd/ld/go.c
のloaddynimport
関数内:name
という変数がlookup
に渡された後、free(name);
が追加されています。このname
も、おそらくsmprint
のような関数によって動的に確保された文字列であると推測されます(コミットの差分にはname
の確保箇所は含まれていませんが、free
が追加されていることから明らかです)。
これらの変更は、C言語における「malloc
で確保したメモリはfree
で解放する」という基本的なメモリ管理のルールを徹底したものであり、メモリリークの防止に直接的に寄与します。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語のソースコードリポジトリ (GitHub): https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/
- 本コミットのGerritチェンジリスト: https://golang.org/cl/5569085
参考にした情報源リンク
- C言語のメモリ管理 (
malloc
,free
): - メモリリークに関する情報:
- Go言語のツールチェインに関する一般的な情報:
- https://go.dev/doc/
- https://go.dev/blog/go1.18 (Goの歴史的背景やツールチェインの進化について触れられている場合がある)