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

[インデックス 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言語におけるメモリ管理 (mallocfree)

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つのファイルにわたっています。

  1. src/cmd/5l/asm.c
  2. src/cmd/6l/asm.c
  3. src/cmd/8l/asm.c
  4. 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.csrc/cmd/6l/asm.csrc/cmd/8l/asm.cneedlib 関数内: smprintによって生成された文字列plookupに渡された後、free(p);が追加されています。これは、smprintが動的にメモリを確保するため、そのメモリが不要になった時点で解放する必要があるためです。

  • src/cmd/ld/go.cloaddynimport 関数内: nameという変数がlookupに渡された後、free(name);が追加されています。このnameも、おそらくsmprintのような関数によって動的に確保された文字列であると推測されます(コミットの差分にはnameの確保箇所は含まれていませんが、freeが追加されていることから明らかです)。

これらの変更は、C言語における「mallocで確保したメモリはfreeで解放する」という基本的なメモリ管理のルールを徹底したものであり、メモリリークの防止に直接的に寄与します。

関連リンク

参考にした情報源リンク