-pthread を忘れると std::thread で例外が発生する仕組み

`std::thread` を使用したソースを -pthread オプションを付けずにビルドすると、ビルドエラーにならずに実行時エラーになる仕組みが気になったので調べてみました。

パスやら何やらは x86-64 版の Gentoo Linux のものです。他ディストリビューションでは微妙に異なるかもしれません。

## -pthread オプション

g++ や clang++ で C++11 以降に実装された `std::thread` を使おうとすると、オプション -pthread が必要です。これを忘れると、スレッドを実行しようとした時点で例外が発生してしまいます。

```c++:sample.cpp
#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 なしではリンクエラーになるはずです。

[^1]: 例えば、 -pthread を指定すると暗黙のうちに指定されるプリプロセッサで処理が分けられるなど。

## 結論: 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 の使い方か……

&lt;ぽそ&gt;でも、正直な所、実行時エラーじゃなくてリンクエラーになって欲しいなぁ……&lt;/ぽそ&gt;