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

[インデックス 13280] ファイルの概要

このコミットは、Go言語のランタイム環境の一部であるlib9ライブラリ内のp9getwd()関数におけるメモリリークを修正するものです。具体的には、p9getwd()が内部でp9getenvを使用する際に発生していた、strdupによるメモリ割り当ての解放漏れに対処しています。

コミット

commit d152321cea4ebee18b7b819d29d0718dbc139212
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Jun 5 01:31:23 2012 +0800

    lib9: fix memory leak in p9getwd()
            although the comment says it uses libc's getenv, without NOPLAN9DEFINES
            it actually uses p9getenv which strdups.
    
    R=golang-dev, dave, rsc
    CC=golang-dev
    https://golang.org/cl/6285046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d152321cea4ebee18b7b819d29d0718dbc139212

元コミット内容

lib9: p9getwd()におけるメモリリークを修正。 コメントではlibcgetenvを使用すると書かれているが、NOPLAN9DEFINESがない場合、実際にはstrdupを使用するp9getenvが使われていた。

変更の背景

この変更は、Go言語の初期のランタイム環境において、現在の作業ディレクトリを取得するp9getwd()関数に潜在していたメモリリークを解決するために行われました。 問題の根源は、コード内のコメントと実際の動作との乖離にありました。コメントではlibc(標準Cライブラリ)のgetenv関数を使用すると示唆されていましたが、特定のコンパイルフラグ(NOPLAN9DEFINES)が定義されていない環境では、Goの内部実装であるp9getenvが代わりに呼び出されていました。 p9getenvは、環境変数の値を取得する際に、その文字列を複製するためにstrdup()関数を使用していました。strdup()は新しいメモリを動的に割り当てるため、その結果が適切に解放されないとメモリリークが発生します。このコミットは、この意図しないp9getenvの使用を防ぎ、libcgetenvが正しく使用されるようにすることで、メモリリークを解消することを目的としています。

前提知識の解説

  • lib9: Go言語の初期のランタイム環境において、システムコールや基本的なユーティリティ関数を提供していたライブラリ群です。Go言語がPlan 9オペレーティングシステムの設計思想に強く影響を受けていたため、その名残としてlib9という名称が使われていました。これは、GoプログラムがOSとやり取りするための低レベルなインターフェースを提供していました。
  • p9getwd(): Plan 9系のシステムコールやライブラリ関数に由来する命名規則を持つ関数で、現在の作業ディレクトリ(Current Working Directory, CWD)のパスを取得する役割を担っていました。Unix/Linuxにおけるgetcwd()やWindowsにおける_getcwd()に相当する機能です。
  • libc (標準Cライブラリ): C言語で書かれたプログラムがOSの機能を利用するための標準的な関数群を提供するライブラリです。ファイルI/O、メモリ管理、文字列操作、プロセス制御など、多岐にわたる機能が含まれます。getenv()関数もこのlibcの一部です。
  • getenv(): libcに含まれる関数で、指定された環境変数の値を取得します。通常、この関数が返すポインタは、環境変数テーブル内の既存の文字列を指しており、呼び出し側がそのメモリを解放する必要はありません。
  • p9getenv(): lib9またはGoの内部で定義されていた可能性のある、getenv()に似た機能を提供する関数です。コミットメッセージによると、このp9getenvは内部でstrdup()を使用していたことが問題でした。
  • strdup(): C言語の標準ライブラリ関数の一つで、与えられた文字列を複製し、その複製された文字列を格納するための新しいメモリを動的に割り当てて、そのポインタを返します。この関数によって割り当てられたメモリは、使用後にfree()関数で明示的に解放する必要があります。解放を怠るとメモリリークが発生します。
  • NOPLAN9DEFINES: これは、Go言語のビルドシステムやコンパイルプロセスにおいて使用されるプリプロセッサマクロ(#define)です。このマクロが定義されている場合、GoのソースコードはPlan 9固有の定義や関数ではなく、より一般的なUnix/POSIX互換の定義や関数(例えば、libcの関数)を使用するようにコンパイルされます。このコミットの文脈では、NOPLAN9DEFINESが定義されていないと、getenvの呼び出しが意図せずp9getenvにリダイレクトされてしまう問題がありました。

技術的詳細

このメモリリークは、Goのsrc/lib9/getwd.cファイル内のp9getwd()関数が、環境変数を取得する際に誤った関数パスを使用していたことに起因します。

  1. 意図された動作: コードのコメントや設計意図としては、p9getwd()libcの標準的なgetenv()関数を使用して環境変数を取得するはずでした。libcgetenv()は、通常、内部の静的バッファや環境変数テーブルへのポインタを返すため、呼び出し側がメモリを解放する必要はありません。
  2. 実際の動作: しかし、NOPLAN9DEFINESというプリプロセッサマクロが定義されていないビルド環境では、getenvというシンボルがGoの内部実装であるp9getenvに解決されてしまっていました。これは、GoがPlan 9の環境をエミュレートする際に、標準Cライブラリの関数をGo独自のラッパー関数でオーバーライドするメカニズムがあったためと考えられます。
  3. メモリリークの原因: 問題は、このp9getenvが、取得した環境変数の文字列を返す際に、strdup()関数を使用してその文字列のコピーを作成していた点にあります。strdup()はヒープメモリを動的に割り当てるため、この割り当てられたメモリは、p9getwd()の呼び出し元やp9getenvの内部で明示的にfree()される必要がありました。しかし、この解放処理が欠けていたため、p9getwd()が呼び出されるたびに、strdup()によって割り当てられたメモリが解放されずに残り、メモリリークが発生していました。
  4. 修正方法: 修正は非常にシンプルかつ効果的です。src/lib9/getwd.cのインクルードセクションに#define NOPLAN9DEFINESを追加することで、このファイルがコンパイルされる際に、getenvなどのシンボルがlibcの標準的な定義に解決されるように強制します。これにより、p9getenvが呼び出されることを防ぎ、代わりにlibcgetenvが使用されるようになります。libcgetenvstrdupを使用しないため、メモリリークの問題が解消されます。
  5. #undef getwdの削除: 以前のコードには#undef getwdという行がありましたが、これはgetwdマクロの定義を解除するためのものでした。NOPLAN9DEFINESが追加されたことで、getwdp9getwdにマッピングされるようなPlan 9固有の定義がそもそも適用されなくなるため、この#undefは不要となり削除されました。

この修正は、Goのランタイムが異なるOS環境(特にUnix系)で正しく動作し、メモリ効率を保つ上で重要なものでした。

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

--- a/src/lib9/getwd.c
+++ b/src/lib9/getwd.c
@@ -26,10 +26,9 @@ THE SOFTWARE.
 #include <u.h>
 #include <errno.h>
 #include <sys/stat.h>
+#define NOPLAN9DEFINES
 #include <libc.h>\n
-#undef getwd
-\n
 char*\n
 p9getwd(char *s, int ns)\n
 {

コアとなるコードの解説

変更はsrc/lib9/getwd.cファイル内の2行の削除と1行の追加です。

  1. #define NOPLAN9DEFINES の追加:

    • この行が追加されたことで、getwd.cがコンパイルされる際に、GoのビルドシステムはPlan 9固有の定義やマクロを適用せず、標準Cライブラリ(libc.h)の定義を優先するようになります。
    • これにより、getenvのような関数呼び出しが、Goの内部実装であるp9getenvではなく、システム標準のgetenvに正しく解決されるようになります。システム標準のgetenvは、通常、返された文字列のメモリを呼び出し側が解放する必要がないため、strdupによるメモリリークの問題が解消されます。
  2. #undef getwd の削除:

    • 以前のコードには#undef getwdという行がありましたが、これはgetwdというマクロの定義を解除するためのものでした。
    • NOPLAN9DEFINESが追加されたことで、getwdがPlan 9固有の定義によってマクロとして定義されることがなくなるため、この#undefは冗長となり削除されました。これにより、コードがよりクリーンになり、意図しないマクロの干渉を防ぐことができます。

これらの変更により、p9getwd()関数が環境変数を扱う際のメモリ管理が正しく行われるようになり、メモリリークが修正されました。

関連リンク

参考にした情報源リンク

  • strdup man page: https://man7.org/linux/man-pages/man3/strdup.3.html
  • getenv man page: https://man7.org/linux/man-pages/man3/getenv.3.html
  • Go言語の初期の歴史とPlan 9の影響に関する一般的な情報源 (例: Go言語の公式ドキュメント、関連する技術ブログなど)
  • C言語のプリプロセッサディレクティブに関する一般的な情報源 (例: C言語の教科書、オンラインリファレンスなど)
  • Go言語のソースコードリポジトリ (特にsrc/lib9ディレクトリの歴史)