[インデックス 13319] ファイルの概要
このコミットでは、以下のファイルが変更されました。
test/bench/shootout/binary-tree.c
test/bench/shootout/k-nucleotide.c
test/bench/shootout/threadring.c
test/bench/shootout/timing.sh
コミット
コミットハッシュ: 97300640ca510f1db2c38bdf48e9e82ee4bae983
作者: Shenghou Ma minux.ma@gmail.com
日付: 2012年6月8日 金曜日 02:56:23 +0800
概要: test/bench/shoutout: fix compliation
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/97300640ca510f1db2c38bdf48e9e82ee4bae983
元コミット内容
test/bench/shoutout: fix compliation
-lm must come after the source file, versions of gcc insist this strict order.
On standard compliant systems, we no longer need malloc.h for malloc.
Use pkg-config(1) to get correct glib cflags and libs.
Fix compiler warning in threadring.c and k-nucleotide.c.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6198076
変更の背景
このコミットは、Go言語プロジェクトのベンチマークスイート(test/bench/shootout
ディレクトリ配下)におけるC言語プログラムのコンパイルに関する複数の問題を修正することを目的としています。具体的には、以下の問題に対処しています。
- リンカオプションの順序問題:
gcc
において、数学ライブラリ(-lm
)の指定がソースファイルよりも前に来ると、一部のgcc
バージョンでコンパイルエラーが発生する問題。これは、リンカがライブラリを解決する際に、そのライブラリが提供するシンボルが使用されるオブジェクトファイルよりも後に指定される必要があるという、リンカの動作に起因します。 malloc.h
の不要性: 標準に準拠したシステムでは、malloc
関数を使用するためにmalloc.h
を明示的にインクルードする必要がなくなっているため、冗長なインクルードを削除する。これは、C標準ライブラリの進化と、stdlib.h
がmalloc
の宣言を含むようになったことによるものです。glib
のコンパイルフラグとライブラリの取得:glib
ライブラリを使用する際に、手動でインクルードパス(-I
)やライブラリパス(-L
)、ライブラリ名(-l
)を指定するのではなく、pkg-config
ユーティリティを使用して、システムに応じた正しいコンパイルフラグとライブラリ情報を動的に取得するように変更する。これにより、ビルドの移植性と堅牢性が向上します。- コンパイラ警告の修正:
threadring.c
とk-nucleotide.c
におけるコンパイラ警告を修正し、コードの品質と移植性を向上させる。特に、ポインタと整数の間のキャストに関する警告や、関数ポインタの型不一致に関する警告が対象です。
これらの修正は、ベンチマークの正確な実行と、異なる環境でのコンパイルの成功を保証するために重要です。
前提知識の解説
1. Computer Language Benchmarks Game (CLBG)
test/bench/shootout
ディレクトリは、Computer Language Benchmarks Game (CLBG) のベンチマークプログラムを含んでいます。CLBGは、様々なプログラミング言語で書かれた共通のアルゴリズムの実装を比較し、実行速度やメモリ使用量などのパフォーマンスを評価するプロジェクトです。Go言語のベンチマークスイートには、他の言語(この場合はC言語)で書かれた参照実装が含まれており、Go言語の実装と比較するために使用されます。
2. GCC (GNU Compiler Collection)
GCCは、GNUプロジェクトによって開発されたコンパイラシステムです。C、C++、Objective-C、Fortran、Ada、Goなど、多くのプログラミング言語をサポートしています。このコミットでは、C言語のコンパイルとリンクの挙動が問題となっています。
3. リンカとライブラリの順序 (-lm
)
C/C++プログラムをコンパイルする際、コンパイラはソースコードをオブジェクトファイルに変換し、その後リンカがこれらのオブジェクトファイルと必要なライブラリを結合して実行可能ファイルを生成します。
gcc
コマンドで-l
オプションを使ってライブラリをリンクする場合、その順序は重要です。リンカは通常、コマンドラインで指定された順序でファイルを処理し、未解決のシンボルを解決しようとします。もしライブラリが、そのライブラリ内の関数を使用するオブジェクトファイルよりも前に指定された場合、リンカはその関数がまだ必要とされていないと判断し、ライブラリを適切にリンクしないことがあります。
-lm
は数学ライブラリ(libm.a
またはlibm.so
)をリンクするためのオプションで、sqrt()
やsin()
などの数学関数が含まれています。このコミットの修正は、nbody.c
がlibm
の関数を使用しているため、nbody.c
のオブジェクトファイルが-lm
よりも前に来るように順序を変更しています。
4. malloc.h
とstdlib.h
malloc.h
は、C言語で動的メモリ割り当てを行う関数(malloc
, free
, calloc
, realloc
など)の宣言を含むヘッダファイルです。しかし、C89標準以降、これらの関数はstdlib.h
にも宣言されるようになりました。現代の標準に準拠したCコンパイラでは、malloc
を使用するためにstdlib.h
をインクルードするだけで十分であり、malloc.h
は冗長または非標準と見なされることがあります。このコミットは、この現代的な慣習に従い、不要なmalloc.h
のインクルードを削除しています。
5. pkg-config
pkg-config
は、ソフトウェアのコンパイル時に必要なライブラリのコンパイルフラグ(-I
で指定するインクルードパスなど)やリンクフラグ(-L
で指定するライブラリパス、-l
で指定するライブラリ名など)を自動的に取得するためのコマンドラインユーティリティです。これにより、異なるシステムやライブラリのバージョンに依存するビルドスクリプトの移植性が向上します。
例えば、pkg-config glib-2.0 --cflags --libs
と実行すると、glib-2.0
ライブラリをコンパイル・リンクするために必要なすべてのフラグが出力されます。
6. glib
GLibは、GTK+やGNOMEプロジェクトで広く使用されている、汎用的なユーティリティ関数とデータ構造を提供するC言語ライブラリです。このコミットでは、k-nucleotide.c
がglib
のGPtrArray
(ポインタの配列)を使用しており、その要素を解放するためにg_ptr_array_foreach
関数を使用しています。
7. GFunc
型と関数ポインタのキャスト
GFunc
はGLibで定義されている関数ポインタの型で、g_ptr_array_foreach
のような関数でコールバックとして使用されます。C言語では、異なる型の関数ポインタを直接渡すとコンパイラ警告や未定義動作を引き起こす可能性があります。このコミットでは、free
関数(void (*)(void*)
のようなシグネチャを持つ)をGFunc
型(void (*)(gpointer data, gpointer user_data)
のようなシグネチャを持つ)に明示的にキャストすることで、型安全性を確保し、コンパイラ警告を解消しています。
8. uintptr_t
とポインタ/整数のキャスト
uintptr_t
は、ポインタを保持できる十分な大きさを持つ符号なし整数型です。これは、ポインタを整数に変換したり、整数をポインタに変換したりする際に、ポインタのサイズがシステムによって異なる場合でも、移植性のある方法で安全に変換するために使用されます。
pthread_create
のような関数は、スレッド関数にvoid*
型の引数を渡します。このコミットでは、スレッドID(整数)をvoid*
として渡す際に、直接int
からvoid*
にキャストするのではなく、一度uintptr_t
を介することで、ポインタのサイズと整数のサイズが異なるアーキテクチャでの潜在的な問題を回避し、コンパイラ警告を解消しています。
技術的詳細
このコミットは、C言語のコンパイルとリンクにおけるいくつかの一般的な落とし穴と、それらを回避するためのベストプラクティスを示しています。
-
リンカの順序依存性:
timing.sh
スクリプト内のnbody
ベンチマークのコンパイルコマンドが、gcc -O2 -lm nbody.c
からgcc -O2 nbody.c -lm
に変更されました。これは、gcc
(特に古いバージョンや特定の構成)が、ライブラリを解決する際に、そのライブラリが提供するシンボルを使用するオブジェクトファイルがコマンドライン上でライブラリよりも前に現れることを要求するためです。nbody.c
がlibm
の関数(例:sqrt
,sin
,cos
など)を使用している場合、nbody.o
が先に処理され、その後に-lm
が指定されることで、リンカはnbody.o
で参照されている数学関数をlibm
から正しく解決できるようになります。 -
malloc.h
の削除:binary-tree.c
から#include <malloc.h>
が削除されました。これは、C89標準以降、malloc
やfree
などの動的メモリ割り当て関数がstdlib.h
で宣言されるようになったためです。現代のCコンパイラ環境では、stdlib.h
をインクルードするだけでこれらの関数は利用可能であり、malloc.h
は非標準または冗長なインクルードと見なされます。この変更は、コードの標準準拠性を高め、不要な依存関係を排除します。 -
pkg-config
によるglib
のリンク:timing.sh
スクリプト内のk-nucleotide
ベンチマークのコンパイルコマンドが、手動でインクルードパスとライブラリを指定するgcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0
から、pkg-config
を使用するgcc -O2 k-nucleotide.c $(pkg-config glib-2.0 --cflags --libs)
に変更されました。pkg-config
は、システムにインストールされているライブラリのバージョンやパスに依存せず、正しいコンパイルフラグ(--cflags
)とリンクフラグ(--libs
)を動的に取得できるため、ビルドスクリプトの移植性とメンテナンス性が大幅に向上します。これにより、異なるLinuxディストリビューションやglib
のインストールパスが異なる環境でも、k-nucleotide.c
が正しくコンパイル・リンクされるようになります。 -
GFunc
への明示的なキャスト:k-nucleotide.c
では、g_ptr_array_foreach
関数にfree
関数をコールバックとして渡す際に、g_ptr_array_foreach(roots, free, NULL);
からg_ptr_array_foreach(roots, (GFunc)free, NULL);
へと変更されました。g_ptr_array_foreach
は、第2引数としてGFunc
型の関数ポインタを期待します。free
関数のシグネチャは通常void free(void *ptr)
であり、GFunc
のシグネチャ(void (*)(gpointer data, gpointer user_data)
)とは異なります。C言語では、異なる型の関数ポインタ間の暗黙的な変換は未定義動作を引き起こす可能性があり、コンパイラ警告の一般的な原因となります。明示的に(GFunc)free
とキャストすることで、コンパイラに意図を伝え、型不一致の警告を解消し、コードの堅牢性を高めます。 -
uintptr_t
を用いたポインタ/整数の安全なキャスト:threadring.c
では、pthread_create
にスレッドID(整数)をvoid*
として渡す際、およびスレッド関数内でvoid*
引数を整数にキャストする際に、int l = (int)num;
とpthread_create(&cthread, &stack_attr, thread, (void*)i);
がそれぞれint l = (int)(uintptr_t)num;
とpthread_create(&cthread, &stack_attr, thread, (void*)(uintptr_t)i);
に変更されました。 また、この変更に伴い、#include <stdint.h>
が追加されました。stdint.h
は、uintptr_t
のような固定幅整数型を定義しています。void*
は汎用ポインタ型ですが、そのサイズはシステムによって異なります(32ビットシステムでは4バイト、64ビットシステムでは8バイトなど)。int
も同様にサイズが異なる場合があります。int
からvoid*
への直接キャスト、またはその逆のキャストは、ポインタのサイズがint
のサイズよりも大きいシステムでは情報が失われる可能性があり、コンパイラ警告や実行時エラーの原因となります。uintptr_t
は、ポインタを保持できることが保証されている整数型であるため、int
->uintptr_t
->void*
、またはvoid*
->uintptr_t
->int
という中間ステップを踏むことで、ポインタと整数の間の変換を安全かつ移植性のある方法で行うことができます。これにより、コンパイラ警告が解消され、異なるアーキテクチャでの潜在的なバグが回避されます。
コアとなるコードの変更箇所
test/bench/shootout/binary-tree.c
--- a/test/bench/shootout/binary-tree.c
+++ b/test/bench/shootout/binary-tree.c
@@ -36,7 +36,6 @@ POSSIBILITY OF SUCH DAMAGE.\n icc -O3 -ip -unroll -static binary-trees.c -lm\n */\n \n-#include <malloc.h>\n #include <math.h>\n #include <stdio.h>\n #include <stdlib.h>\
test/bench/shootout/k-nucleotide.c
--- a/test/bench/shootout/k-nucleotide.c
+++ b/test/bench/shootout/k-nucleotide.c
@@ -221,7 +221,7 @@ main ()\n \n free(s);\n \n- g_ptr_array_foreach(roots, free, NULL);\n+ g_ptr_array_foreach(roots, (GFunc)free, NULL);\n g_ptr_array_free(roots, TRUE);\n \n return 0;\
test/bench/shootout/threadring.c
--- a/test/bench/shootout/threadring.c
+++ b/test/bench/shootout/threadring.c
@@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE.\n * contributed by Premysl Hruby\n */\n \n+#include <stdint.h>\n #include <stdio.h>\n #include <stdlib.h>\n #include <pthread.h>\
@@ -57,7 +58,7 @@ static struct stack stacks[THREADS];\n \n static void* thread(void *num)\n {\n- int l = (int)num;\n+ int l = (int)(uintptr_t)num;\n int r = (l+1) % THREADS;\n int token;\n \
@@ -94,7 +95,7 @@ int main(int argc, char **argv)\n pthread_mutex_lock(mutex + i);\n \n pthread_attr_setstack(&stack_attr, &stacks[i], sizeof(struct stack));\n- pthread_create(&cthread, &stack_attr, thread, (void*)i);\n+ pthread_create(&cthread, &stack_attr, thread, (void*)(uintptr_t)i);\n }\n \n pthread_mutex_unlock(mutex + 0);\
test/bench/shootout/timing.sh
--- a/test/bench/shootout/timing.sh
+++ b/test/bench/shootout/timing.sh
@@ -97,7 +97,7 @@ revcomp() {\n \n nbody() {\n \trunonly echo \'nbody -n 50000000\'\n-\trun \'gcc -O2 -lm nbody.c\' a.out 50000000\n+\trun \'gcc -O2 nbody.c -lm\' a.out 50000000\n \trun \'gccgo -O2 nbody.go\' a.out -n 50000000\n \trun \'gc nbody\' $O.out -n 50000000\n \trun \'gc_B nbody\' $O.out -n 50000000\n@@ -147,7 +147,7 @@ knucleotide() {\n \trunonly gcc -O2 fasta.c\n \trunonly a.out 1000000 > x # should be using 25000000\n \trunonly echo \'k-nucleotide 1000000\'\n-\trun \'gcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0\' a.out <x\n+\trun \"gcc -O2 k-nucleotide.c $(pkg-config glib-2.0 --cflags --libs)\" a.out <x\n \trun \'gccgo -O2 k-nucleotide.go\' a.out <x\n \trun \'gccgo -O2 k-nucleotide-parallel.go\' a.out <x\n \trun \'gc k-nucleotide\' $O.out <x\
コアとなるコードの解説
binary-tree.c
におけるmalloc.h
の削除
- 変更前:
#include <malloc.h>
- 変更後: 行が削除
この変更は、malloc.h
のインクルードを削除しています。現代のC標準(C89以降)では、malloc
やfree
といった動的メモリ割り当て関数はstdlib.h
で宣言されています。したがって、stdlib.h
が既にインクルードされている場合、malloc.h
は冗長であり、一部の環境では非標準のヘッダとして警告やエラーの原因となる可能性があります。この修正により、コードの標準準拠性が向上し、よりクリーンになります。
k-nucleotide.c
におけるGFunc
への明示的なキャスト
- 変更前:
g_ptr_array_foreach(roots, free, NULL);
- 変更後:
g_ptr_array_foreach(roots, (GFunc)free, NULL);
g_ptr_array_foreach
関数は、配列の各要素に対してコールバック関数を適用します。このコールバック関数はGFunc
型である必要があります。free
関数のシグネチャは通常void free(void *ptr)
であり、GFunc
の期待するシグネチャ(void (*)(gpointer data, gpointer user_data)
)とは異なります。C言語では、異なる型の関数ポインタを直接渡すとコンパイラ警告が発生することがあります。(GFunc)free
と明示的にキャストすることで、コンパイラに型変換の意図を伝え、警告を解消し、コードの型安全性を向上させています。
threadring.c
におけるuintptr_t
の使用とstdint.h
の追加
- 変更前:
int l = (int)num; pthread_create(&cthread, &stack_attr, thread, (void*)i);
- 変更後:
#include <stdint.h> // 追加 // ... int l = (int)(uintptr_t)num; // ... pthread_create(&cthread, &stack_attr, thread, (void*)(uintptr_t)i);
この変更は、ポインタと整数の間のキャストをより安全かつ移植性のある方法で行うためにuintptr_t
を使用しています。
pthread_create
は、スレッド関数にvoid*
型の引数を渡します。ここでは、スレッドの識別子(整数i
)をvoid*
として渡しています。また、スレッド関数thread
内では、そのvoid*
引数num
を整数l
にキャストして使用しています。
int
とvoid*
のサイズはシステムによって異なる可能性があり、直接キャストすると情報が失われたり、未定義動作を引き起こしたりする可能性があります。uintptr_t
は、ポインタを保持できることが保証されている符号なし整数型です。
int
-> uintptr_t
-> void*
、およびvoid*
-> uintptr_t
-> int
という中間キャストを行うことで、ポインタのサイズと整数のサイズが異なるアーキテクチャでも、データが正しく保持され、コンパイラ警告が解消されます。このために、uintptr_t
が定義されている<stdint.h>
がインクルードされています。
timing.sh
におけるgcc
リンカ順序とpkg-config
の使用
-
nbody
ベンチマークの変更:- 変更前:
run 'gcc -O2 -lm nbody.c' a.out 50000000
- 変更後:
run 'gcc -O2 nbody.c -lm' a.out 50000000
この変更は、gcc
のリンカにおけるライブラリの順序問題を修正しています。数学ライブラリ-lm
は、それを使用するソースファイル(nbody.c
)の後に指定される必要があります。これにより、リンカがnbody.c
で参照される数学関数を正しく解決できるようになります。
- 変更前:
-
k-nucleotide
ベンチマークの変更:- 変更前:
run 'gcc -O2 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include k-nucleotide.c -lglib-2.0' a.out <x
- 変更後:
run "gcc -O2 k-nucleotide.c $(pkg-config glib-2.0 --cflags --libs)" a.out <x
この変更は、glib
ライブラリのコンパイルとリンクにpkg-config
ユーティリティを使用するように切り替えています。以前は手動でインクルードパス(-I
)とライブラリ名(-l
)を指定していましたが、これはシステムやglib
のバージョンによってパスが異なる場合に問題を引き起こす可能性がありました。$(pkg-config glib-2.0 --cflags --libs)
は、glib-2.0
ライブラリをコンパイル・リンクするために必要なすべてのフラグ(インクルードパス、ライブラリパス、ライブラリ名など)を自動的に出力します。これにより、ビルドスクリプトの移植性と堅牢性が大幅に向上し、異なる環境でのコンパイルが容易になります。
- 変更前:
関連リンク
- Computer Language Benchmarks Game: https://benchmarksgame-team.pages.debian.net/benchmarksgame/
- GCC Documentation: https://gcc.gnu.org/onlinedocs/
- GLib Reference Manual: https://docs.gtk.org/glib/
pkg-config
man page: https://linux.die.net/man/1/pkg-configstdint.h
(C Standard Library): https://en.cppreference.com/w/c/types/integer
参考にした情報源リンク
- GCC Linker Order: https://stackoverflow.com/questions/45135/why-does-the-order-of-libraries-in-gcc-matter
malloc.h
vsstdlib.h
: https://stackoverflow.com/questions/1005081/malloc-h-vs-stdlib-hpkg-config
usage: https://www.freedesktop.org/wiki/Software/pkg-config/- Casting function pointers: https://stackoverflow.com/questions/1005081/malloc-h-vs-stdlib-h (Note: This link was used for
malloc.h
but the general concept of casting applies to function pointers as well) uintptr_t
usage: https://stackoverflow.com/questions/1845482/what-is-uintptr-t-in-cpthread_create
arguments: https://man7.org/linux/man-pages/man3/pthread_create.3.html