std::thread
を使用したソースを -pthread オプションを付けずにビルドすると、ビルドエラーにならずに実行時エラーになる仕組みが気になったので調べてみました。
パスやら何やらは x86-64 版の Gentoo Linux のものです。他ディストリビューションでは微妙に異なるかもしれません。
-pthread オプション
g++ や clang++ で C++11 以降に実装された std::thread
を使おうとすると、オプション -pthread が必要です。これを忘れると、スレッドを実行しようとした時点で例外が発生してしまいます。
#include <iostream>
#include <thread>
void func()
{
std::cout << "Hello!" << std::endl;
}
int main()
{
std::thread(func).join();
}
$ g++ -Wall -std=c++11 sample.cpp -o sample1 # ← sample1 は -pthread 付けない
$ g++ -Wall -std=c++11 -pthread sample.cpp -o sample2 # ← sample2 は -pthread 付ける
$ ./sample1
terminate called after throwing an instance of 'std::system_error'
what(): Enable multithreading to use std::thread: Operation not permitted
中止
$ ./sample2
Hello!
実は libpthread をリンクするかどうかだけの違い
実のところ、-pthread を付けず、リンクの時に -lpthread を付けるだけでも挙動が変化します。
$ g++ -Wall -std=c++11 -c sample.cpp
$ g++ sample.o -o sample3 # ← sample3 は -lpthread 付けない
$ g++ sample.o -lpthread -o sample4 # ← sample4 は -lpthread 付ける
$ ./sample3
terminate called after throwing an instance of 'std::system_error'
what(): Enable multithreading to use std::thread: Operation not permitted
中止
$ ./sample4
Hello!
さらに、libpthread をリンクしていない版で、LD_PRELOAD に libpthread を指定して実行すると、これまた動いてしまいます。
$ env LD_PRELOAD=/lib/libpthread.so.0 ./sample1
Hello!
$ env LD_PRELOAD=/lib/libpthread.so.0 ./sample3
Hello!
これらのことより、実行時エラーになるか否かという違いは、コンパイル時に決まる1のではなく、単純に libpthread がリンクされているかどうかで決まっていることが分かります。
単純に、 libpthread の関数が定義されていないだけだと、 -lpthread なしではリンクエラーになるはずです。
結論: weak シンボルでした
となると、ライブラリのどこかにダミーの pthread 関数が定義されているのではないかと調べてみました。
すると、libstdc++ に、 pthread 関数と同名の関数が weak シンボルとして定義されていました。
$ lddtree sample1
sample1 (interpreter => /lib64/ld-linux-x86-64.so.2)
libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/libstdc++.so.6
libm.so.6 => /lib64/libm.so.6
libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/libgcc_s.so.1
libc.so.6 => /lib64/libc.so.6
$ readelf -s -W /usr/lib/gcc/x86_64-pc-linux-gnu/4.9.3/libstdc++.so.6 | c++filt
シンボルテーブル '.dynsym' は 3862 個のエントリから構成されています:
番号: 値 サイズ タイプ Bind Vis 索引名
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000059060 0 SECTION LOCAL DEFAULT 9
2: 00000000002f0100 0 SECTION LOCAL DEFAULT 18
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __strtof_l@GLIBC_2.2.5 (33)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fileno@GLIBC_2.2.5 (33)
5: 0000000000000000 0 FUNC WEAK DEFAULT UND pthread_cond_destroy@GLIBC_2.3.2 (34)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __strcoll_l@GLIBC_2.2.5 (33)
(中略)
24: 0000000000000000 0 FUNC WEAK DEFAULT UND pthread_cond_signal@GLIBC_2.3.2 (34)
(中略)
33: 0000000000000000 0 NOTYPE WEAK DEFAULT UND pthread_key_create
(後略)
libstdc++ にシンボルが定義されているから、 libpthread がリンクされていなくても libstdc++ で定義された関数が実行され、それが weak シンボルだから、 libpthread がリンクされる (LD_PRELOAD で後付けされる場合も含む) と、libpthread で定義された関数の方が優先される、と。
なるほど、これが weak の使い方か……
<ぽそ>でも、正直な所、実行時エラーじゃなくてリンクエラーになって欲しいなぁ……</ぽそ>
-
例えば、 -pthread を指定すると暗黙のうちに指定されるプリプロセッサで処理が分けられるなど。 ↩