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

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

このコミットは、Go言語のmisc/cgo/testディレクトリ内のテストコードにおける、C言語のstatic関数名の衝突問題を解決します。具体的には、issue5337.goissue3350.goという2つのテストファイル内で定義されていたstatic関数threadが、-linkmode=internalという特殊なリンクモードを使用した場合に衝突を起こす問題に対処しています。解決策として、issue5337.go内のstatic関数threadの名前をthread1に変更することで、衝突を回避しています。

コミット

commit 41fd4f988ce2297ea6d2bf7755e753638a8cf33a
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jul 11 23:24:35 2013 -0400

    misc/cgo/test: make test work with -linkmode=internal
    
    The static func named thread in issue5337.go's C snippet
    conflicts with the static func named thread in issue3350.go's C snippet.
    I don't know why (they're both static) but I also don't care,
    because -linkmode=internal only needs to be able to handle
    the cgo in the standard library, and it does.
    
    Change the test to avoid this problem.
    
    Fixes build (after run.bash is fixed to detect the breakage).
    
    R=minux.ma
    TBR=minux.ma
    CC=golang-dev
    https://golang.org/cl/11201043

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

https://github.com/golang/go/commit/41fd4f988ce2297ea6d2bf7755e753638a8cf33a

元コミット内容

このコミットは、misc/cgo/testディレクトリ内のテストが-linkmode=internalオプションで動作するように修正するものです。issue5337.goのCコードスニペットにあるstatic関数threadが、issue3350.goのCコードスニペットにある同じくstatic関数threadと衝突していました。コミットの作者であるRuss Coxは、両者がstaticであるにもかかわらず衝突する理由は不明だが、-linkmode=internalは標準ライブラリ内のcgoを処理できれば十分であるため、この問題に深く関心はないと述べています。この問題はテストコードの変更によって回避され、ビルドの破損を修正しました。

変更の背景

この変更の背景には、Goのcgoテストスイートにおける特定のリンキングモードでの問題がありました。Goのビルドシステムには、CコードとGoコードをリンクする際にいくつかのモードがあります。その一つが-linkmode=internalです。

通常、C言語のstaticキーワードで宣言された関数や変数は、その定義されたファイル(翻訳単位)内でのみ可視であり、外部からはアクセスできません(内部リンケージ)。そのため、異なるファイルで同じ名前のstatic関数が定義されていても、通常は名前の衝突は発生しません。

しかし、このコミットメッセージによると、misc/cgo/test/issue5337.gomisc/cgo/test/issue3350.goという2つのテストファイルに含まれるCコードスニペット内で、それぞれstatic void *thread(void *p)という同じ名前の関数が定義されており、これが-linkmode=internalというGoのビルドオプションを使用した場合に衝突を引き起こしていました。

この衝突は、GoのビルドシステムがCコードをどのようにコンパイルし、リンクするかに起因する可能性があります。特に-linkmode=internalは、GoのリンカがCのオブジェクトファイルを直接処理しようとする場合に、通常は隠蔽されるべきstaticシンボルが何らかの形で可視になってしまう、あるいはシンボルテーブルの処理方法に特殊性があるために発生したと考えられます。コミットメッセージの作者自身が「なぜ衝突するのか分からない」と述べていることから、これはGoのビルドシステムの深部に潜む、当時としては予期せぬ挙動であったことが伺えます。

この衝突により、テストスイートのビルドが失敗していたため、テストを通過させるためにこの変更が必要となりました。

前提知識の解説

このコミットを理解するためには、以下の概念についての前提知識が必要です。

  1. cgo: Go言語からC言語のコードを呼び出すためのGoの機能です。Goプログラム内でCの関数やデータ構造を利用できるようにします。import "C"という特殊なインポート宣言を使用し、GoコードとCコードの間の相互運用を可能にします。
  2. C言語のstaticキーワード:
    • 関数に対するstatic: 関数にstaticキーワードを付けると、その関数は定義されたソースファイル(翻訳単位)内でのみ可視となります。つまり、他のソースファイルからはその関数を呼び出すことができません。これは「内部リンケージ」と呼ばれ、名前の衝突を防ぐために使用されます。
    • 変数に対するstatic: グローバル変数にstaticを付けると、その変数は定義されたソースファイル内でのみ可視となります。ローカル変数にstaticを付けると、その変数は関数の呼び出しが終了しても値が保持され続けます(静的記憶域期間)。
  3. リンケージ (Linkage): プログラムの異なる部分で定義された識別子(関数名や変数名)が、同じ実体を参照するかどうかを決定する規則です。C言語には主に以下の3種類のリンケージがあります。
    • 外部リンケージ (External Linkage): 複数の翻訳単位間で共有される識別子(例: externなしのグローバル変数、通常の関数)。
    • 内部リンケージ (Internal Linkage): 単一の翻訳単位内でのみ共有される識別子(例: staticなグローバル変数や関数)。
    • リンケージなし (No Linkage): 識別子がそのスコープ内でのみ有効で、他の場所からは参照できない(例: ローカル変数)。
  4. pthread_create: POSIXスレッドライブラリ(Pthreads)の一部で、新しいスレッドを作成するための関数です。
    • int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    • start_routine引数には、新しく作成されるスレッドが実行を開始する関数のポインタを渡します。
  5. pthread_kill: 特定のスレッドにシグナルを送信するための関数です。
    • int pthread_kill(pthread_t thread, int sig);
  6. SIGPROF: プロファイリングタイマーが満了したときにプロセスに送信されるシグナルです。通常、プログラムの実行プロファイルを収集するために使用されます。
  7. Goの-linkmode=internalビルドオプション: Goのビルドツール(go build)に渡されるオプションの一つで、Goの内部リンカを使用してCgoコードをリンクするように指示します。通常、Cgoはシステムリンカ(gccなど)を呼び出してCコードをコンパイル・リンクしますが、-linkmode=internalを使用すると、GoのリンカがCのオブジェクトファイルを直接取り込み、Goのオブジェクトファイルと結合しようとします。これは、クロスコンパイル環境や、特定のビルド要件がある場合に利用されることがあります。このモードでは、システムリンカの挙動とは異なるシンボル解決のルールが適用される可能性があり、それが今回のstatic関数の衝突問題を引き起こしたと考えられます。

技術的詳細

この問題の核心は、C言語のstaticキーワードの意図された挙動と、Goの-linkmode=internalオプションがその挙動に与える影響の間にあったと考えられます。

C言語において、static関数は内部リンケージを持つため、異なるソースファイルで同じ名前のstatic関数が定義されていても、それらは別々の実体として扱われ、名前の衝突は発生しないはずです。各static関数は、その定義された翻訳単位(この場合はissue5337.goissue3350.go内のCコードスニペットがそれぞれ独立した翻訳単位として扱われる)のローカルスコープに限定されます。

しかし、Goのcgoは、GoとCのコードを統合するために、Cコードをコンパイルし、その結果生成されたオブジェクトファイルをGoのビルドプロセスに組み込みます。通常、cgoはシステムリンカ(例えばGCC)を利用してCコードをコンパイル・リンクします。この場合、システムリンカはstatic関数の内部リンケージを適切に処理し、シンボル衝突は発生しません。

問題は-linkmode=internalオプションが使用された場合に発生しました。このオプションは、GoのビルドツールがCコードのオブジェクトファイルをGoの内部リンカで直接処理しようとすることを意味します。Goの内部リンカは、GoのオブジェクトファイルとCのオブジェクトファイルを結合する際に、システムリンカとは異なるシンボル解決のロジックを持つ可能性があります。

考えられるシナリオとしては、以下のいずれかが原因でstatic関数の衝突が発生した可能性があります。

  1. シンボルテーブルの統合: Goの内部リンカが、異なるCソースファイルから生成されたオブジェクトファイルのシンボルテーブルを統合する際に、staticシンボルを適切にスコープ分離せず、グローバルシンボルとして扱ってしまった。
  2. 名前マングリングの欠如: Cコンパイラがstatic関数に対して、ファイルごとに異なる名前マングリング(シンボル名を一意にするための変換)を行わない場合、内部リンカがそれらを区別できなくなる。
  3. テスト環境の特殊性: misc/cgo/testディレクトリ内のテストは、Goのcgo機能の様々なエッジケースや特定の挙動を検証するために書かれています。これらのテストは、通常のアプリケーション開発では遭遇しないような、リンカやコンパイラの挙動の限界を試す場合があります。今回の衝突も、そのような特殊なテスト環境下で、Goの内部リンカの特定の挙動が露呈した結果である可能性が高いです。

コミットメッセージの作者が「なぜ衝突するのか分からない」と述べているのは、C言語のstaticのセマンティクスからすれば予期せぬ挙動であったためです。しかし、この問題がGoの標準ライブラリのcgo機能の動作に影響を与えない(つまり、標準ライブラリのcgoコードは-linkmode=internalで正しく動作する)ため、テストコード側で回避策を講じることで、根本原因の深掘りよりもビルドの修正を優先したという判断が伺えます。

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

変更はmisc/cgo/test/issue5337.goファイル内で行われました。

--- a/misc/cgo/test/issue5337.go
+++ b/misc/cgo/test/issue5337.go
@@ -10,14 +10,14 @@ package cgotest
 #include <signal.h>
 #include <pthread.h>
 
-static void *thread(void *p) {
+static void *thread1(void *p) {
  	(void)p;
  	pthread_kill(pthread_self(), SIGPROF);\
  	return NULL;
 }
 void test5337() {
  	pthread_t tid;
- 	pthread_create(&tid, 0, thread, NULL);
+ 	pthread_create(&tid, 0, thread1, NULL);
  	pthread_join(tid, 0);
 }
 */

具体的には、以下の2箇所が変更されました。

  1. static void *thread(void *p) {static void *thread1(void *p) { に変更されました。
  2. pthread_create(&tid, 0, thread, NULL);pthread_create(&tid, 0, thread1, NULL); に変更されました。

コアとなるコードの解説

この変更は非常にシンプルで、issue5337.go内のCコードスニペットで定義されていたstatic関数threadの名前をthread1にリネームしただけです。

  • static void *thread(void *p) から static void *thread1(void *p) への変更: これにより、issue5337.go内で定義されるC関数がthread1という名前になり、issue3350.go内で定義されている同じくstaticthread関数との名前の衝突が物理的に回避されます。たとえGoの内部リンカがstaticシンボルを適切に分離できない場合でも、異なる名前を持つことでシンボル衝突は発生しなくなります。

  • pthread_create(&tid, 0, thread, NULL) から pthread_create(&tid, 0, thread1, NULL) への変更: pthread_create関数の第3引数は、新しく作成されるスレッドが実行を開始する関数のポインタです。関数名をthreadからthread1に変更したため、この呼び出しも新しい関数名に合わせて更新する必要があります。これにより、Goのテストコードが意図したC関数(thread1)を正しく呼び出すことが保証されます。

この変更は、問題の根本原因(Goの内部リンカがstaticシンボルをどのように扱うか)を解決するものではなく、あくまでテストコード側で衝突を回避するための実用的なワークアラウンドです。コミットメッセージの作者が「なぜ衝突するのか分からないが、気にしない」と述べているのは、この問題がGoの標準ライブラリのcgo機能の動作に影響を与えず、テストスイートのビルドを修正することが喫緊の課題であったためと考えられます。

関連リンク

参考にした情報源リンク

  • Goのソースコードリポジトリ (golang/go): https://github.com/golang/go
  • Goのコードレビューシステム (Gerrit): https://go.dev/cl/11201043 (コミットメッセージに記載されているChange-ID)
  • C言語のstaticキーワードに関する一般的な情報源 (例: C言語の教科書、オンラインリファレンス)
  • Goのビルドオプションに関する情報源 (例: go help buildコマンドの出力、Goの公式ドキュメント)
  • Pthreads APIに関する情報源 (例: POSIX標準ドキュメント、manページ)
  • Goのissue5337およびissue3350に関する情報 (Goのissueトラッカーや関連するコミット履歴)