静的オブジェクトの破棄順問題

# 静的オブジェクトの破棄時にクラッシュする例

とある市販ソフトが macOS がシャットダウン（ログアウト?）するときに異常終了する。実害は見当たらない。

異常終了するのは、ログインから起動しっぱなしのプロセス。

<details><summary>クラッシュ ログの抜粋 (\*\*\*は伏せた箇所) </summary><div>

```
Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x00000000000000b0
Exception Note:        EXC_CORPSE_NOTIFY

Termination Signal:    Segmentation fault: 11
Termination Reason:    Namespace SIGNAL, Code 0xb
Terminating Process:   exc handler [2226]

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
(略)
3   libsystem_platform.dylib            0x00007fff7031142d _sigtramp + 29
4   ???                                 000000000000000000 0 + 0
5   libsystem_c.dylib                   0x00007fff701e6a1c abort + 120
6   libc++abi.dylib                     0x00007fff6d284be8 abort_message + 231
7   libc++abi.dylib                     0x00007fff6d284cc6 demangling_terminate_handler() + 48
8   libc++abi.dylib                     0x00007fff6d291dc7 std::__terminate(void (*)()) + 8
9   libc++abi.dylib                     0x00007fff6d291d88 std::terminate() + 56
10  ***                                 0x0000000104a844c4 std::__1::unique_ptr<***Thread, std::__1::default_delete<***Thread> >::~unique_ptr() + 68
11  libsystem_c.dylib                   0x00007fff701c1446 __cxa_finalize_ranges + 319
12  libsystem_c.dylib                   0x00007fff701c171c exit + 55
13  com.apple.AppKit                    0x00007fff35f53c03 -[NSApplication terminate:] + 1759
14  com.apple.AppKit                    0x00007fff360dac50 -[NSApplication _terminateFromSender:askIfShouldTerminate:saveWindows:] + 126
15  com.apple.AppKit                    0x00007fff360dab5b __52-[NSApplication(NSAppleEventHandling) _handleAEQuit]_block_invoke + 46
16  com.apple.AppKit                    0x00007fff36122c31 ___NSMainRunLoopPerformBlockInModes_block_invoke + 25
17  com.apple.CoreFoundation            0x00007fff38abd7ab __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
18  com.apple.CoreFoundation            0x00007fff38abd6ed __CFRunLoopDoBlocks + 379
19  com.apple.CoreFoundation            0x00007fff38abc731 __CFRunLoopRun + 1257
20  com.apple.CoreFoundation            0x00007fff38abbbd3 CFRunLoopRunSpecific + 499
21  com.apple.HIToolbox                 0x00007fff3761265d RunCurrentEventLoopInMode + 292
22  com.apple.HIToolbox                 0x00007fff376122a9 ReceiveNextEventCommon + 356
23  com.apple.HIToolbox                 0x00007fff37612127 _BlockUntilNextEventMatchingListInModeWithFilter + 64
24  com.apple.AppKit                    0x00007fff35c83eb4 _DPSNextEvent + 990
25  com.apple.AppKit                    0x00007fff35c82690 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1352
26  com.apple.AppKit                    0x00007fff35c743ae -[NSApplication run] + 658
27  com.***                             0x000000010405b49a -[*** run] + 138
28  com.***                             0x0000000104049d9b main + 1163
29  libdyld.dylib                       0x00007fff701187fd start + 1

Thread 1:
0   libsystem_kernel.dylib              0x00007fff7025bce6 __psynch_cvwait + 10
1   libsystem_pthread.dylib             0x00007fff7031d185 _pthread_cond_wait + 701
(略: ***Thread 関連)
7   libsystem_pthread.dylib             0x00007fff7031ce65 _pthread_start + 148
8   libsystem_pthread.dylib             0x00007fff7031883b thread_start + 15

Thread 2:
0   libsystem_kernel.dylib              0x00007fff7025bce6 __psynch_cvwait + 10
1   libsystem_pthread.dylib             0x00007fff7031d185 _pthread_cond_wait + 701
(略: ***Thread　関連)
5   libsystem_pthread.dylib             0x00007fff7031ce65 _pthread_start + 148
6   libsystem_pthread.dylib             0x00007fff7031883b thread_start + 15

Thread 3:: default
(略)

Thread 4:
(略)

Thread 5:
(略)

Thread 6:
(略)

Thread 7:
(略)

Thread 8:
(略)

Thread 9:
(略)

Thread 10:
(略)

Thread 11:
(略)

Thread 12:
(略)

Thread 13:
(略)

Thread 14:
(略)

Thread 15:
(略)

Thread 16:
(略)

Thread 17:
(略)

Thread 18:: com.apple.NSEventThread
0   libsystem_kernel.dylib              0x00007fff7025925a mach_msg_trap + 10
1   libsystem_kernel.dylib              0x00007fff702595d0 mach_msg + 60
2   com.apple.CoreFoundation            0x00007fff38abdd0b __CFRunLoopServiceMachPort + 322
3   com.apple.CoreFoundation            0x00007fff38abc8e7 __CFRunLoopRun + 1695
4   com.apple.CoreFoundation            0x00007fff38abbbd3 CFRunLoopRunSpecific + 499
5   com.apple.AppKit                    0x00007fff35e26a72 _NSEventThread + 132
6   libsystem_pthread.dylib             0x00007fff7031ce65 _pthread_start + 148
7   libsystem_pthread.dylib             0x00007fff7031883b thread_start + 15

Thread 19:
(略)

Thread 20:
0   libsystem_pthread.dylib             0x00007fff70318818 start_wqthread + 0

Thread 21:
0   libsystem_pthread.dylib             0x00007fff70318818 start_wqthread + 0

```

</div></details>

注目する部分は

```
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
5   libsystem_c.dylib                   0x00007fff701e6a1c abort + 120
(略)
9   libc++abi.dylib                     0x00007fff6d291d88 std::terminate() + 56
10  ***                                 0x0000000104a844c4 std::__1::unique_ptr<***Thread, std::__1::default_delete<***Thread> >::~unique_ptr() + 68
11  libsystem_c.dylib                   0x00007fff701c1446 __cxa_finalize_ranges + 319
12  libsystem_c.dylib                   0x00007fff701c171c exit + 55

Thread 1:
Thread 2:
```

で、exit は静的オブジェクトを破棄するため __cxa_finalize_ranges を呼んでいる。

静的オブジェクトの１つが unique_ptr<\*\*\*Thread,...> の型で存在するらしい。

ここでは、スレッド管理クラス (\*\*\*Thread) と思われるオブジェクトを破棄しようとして失敗していることが覗える。

\*\*\*Thread のデストラクタでスレッドを終了させる仕様ならば、デストラクタで必要とするオブジェクトが既に破棄されている可能性を考えないといけない。

スレッドを終了させてから、\*\*\*Thread のデストラクタを呼ぶ仕様ならば exit 時の対応を忘れていたのだろう。

どちらにしろ 100% 再現するので、終了処理のテストをやっていない。


# 静的オブジェクト 初期化・破棄処理順

静的オブジェクトが破棄される順番は、初期化の順逆になっている。その順番は、翻訳単位（コンパイルで生成される１つのオブジェクト ファイル）の中での順番と、リンク時の順番などで決まる。

g++ の \_\_attribute\_\_((init_priority(N))) を使って、処理順を確認してみる。テストした環境は
> Apple clang version 11.0.0 (clang-1100.0.33.17)
> Target: x86_64-apple-darwin19.2.0

```c++:sample1.cpp
#include <cstdio>

#ifdef NO_ATTRIBUTE
#define __attribute__(x)
#endif

struct ObjA
{
  const char *message;

  ObjA(const char *msg) : message(msg) { printf(" ObjA: %s\n", message); }
  ~ObjA() { printf("~ObjA: %s\n", message); }
};

ObjA __attribute__((init_priority(1002))) a2("a2");
ObjA __attribute__((init_priority(1001))) a1("a1");
ObjA __attribute__((init_priority(1000))) a0("a0");

ObjA &foo()
{
  static ObjA obj_foo("foo");
  return obj_foo;
}

ObjA &bar()
{
  static ObjA obj_bar("bar");
  return obj_bar;
}

int main(int argc, char **argv)
{
  foo();
  bar();
  return 0;
}
```

```c++:sample2.cpp
#include <cstdio>

#ifdef NO_ATTRIBUTE
#define __attribute__(x)
#endif

struct ObjB
{
  const char *message;

  ObjB(const char *msg) : message(msg) { printf(" ObjB: %s\n", message); }
  ~ObjB() { printf("~ObjB: %s\n", message); }
};

ObjB __attribute__((init_priority(1002))) b2("b2");
ObjB __attribute__((init_priority(1001))) b1("b1");
ObjB __attribute__((init_priority(1000))) b0("b0");
```

実行結果は

```
$ g++ -o sample sample1.cpp sample2.cpp
$ ./sample
 ObjA: a0
 ObjA: a1
 ObjA: a2
 ObjB: b0
 ObjB: b1
 ObjB: b2
 ObjA: foo
 ObjA: bar
~ObjA: bar
~ObjA: foo
~ObjB: b2
~ObjB: b1
~ObjB: b0
~ObjA: a2
~ObjA: a1
~ObjA: a0
```

となって、翻訳単位の中では順番の制御ができるようですが、翻訳単位を跨いだ順番の制御はできていません。

init_priority なしでは

```
$ g++ -DNO_ATTRIBUTE -o sample sample1.cpp sample2.cpp
$ ./sample
 ObjA: a2
 ObjA: a1
 ObjA: a0
 ObjB: b2
 ObjB: b1
 ObjB: b0
 ObjA: foo
 ObjA: bar
~ObjA: bar
~ObjA: foo
~ObjB: b0
~ObjB: b1
~ObjB: b2
~ObjA: a0
~ObjA: a1
~ObjA: a2
```

ソース ファイルの順番を入れ替えると

```
$ g++ -DNO_ATTRIBUTE -o sample sample2.cpp sample1.cpp
$ ./sample
 ObjB: b2
 ObjB: b1
 ObjB: b0
 ObjA: a2
 ObjA: a1
 ObjA: a0
 ObjA: foo
 ObjA: bar
~ObjA: bar
~ObjA: foo
~ObjA: a0
~ObjA: a1
~ObjA: a2
~ObjB: b0
~ObjB: b1
~ObjB: b2
```

順番が入れ替わりました。

静的オブジェクトを使うときは、デストラクタで必要とするオブジェクトが先に破棄されないように気をつけなくてはなりません。

しかし、静的オブジェクトの配置場所やリンク順を追って制御するのは、大きなプログラムほど困難です。

デバッガで「実行→中断」の繰り返しで開発していると、初期化順問題は正常に動作しないので直ぐに分かりますが、破棄順問題は気付きません。

たまには終了処理のテストをしましょう。