ページ

2013年11月11日月曜日

翻訳単位をまたいだ静的変数の初期化順序について

STM32で開発しているときに、時間を管理するTimerクラスをつくってました。
宣言を一部抜き出すとこんな感じです。

class Timer
{
public:
  Timer(); //_countsに_countを登録
  ~Timer(); //_countsから_countを除去
  static void Tick(); //この中で_countsに含まれる値をデクリメント

private:
  uint32_t _count;
  static std::list<uint32_t*> _counts;
}

コメントにあるとおり、コンストラクタでstaticなlistに対してpush_backを呼んでいます。

std::list<uint32_t*> Timer::_counts;

Timer::Timer()
  : _count(0)
{
  _counts.push_back(&_count);
}

Timerクラスのインスタンスを普通に作って使う分には問題ないのですが、Timerクラスのインスタンスを静的変数にすると問題が発生します…

class Hoge
{
  static Timer timer;
}

Timer Hoge::timer;

これは、別のソースファイル(==別の翻訳単位)において、依存関係のある静的変数が定義された際に発生する問題です。
C++では翻訳単位をまたいだ静的変数の初期化順序は規定されていないため、
std::listのコンストラクタと、Timerのコンストラクタが呼ばれる順序は未定義になります。
そのため、.oファイルをリンクする順序によって、実行時エラーが発生したりしなかったりします
やっかいですね…

これを解決するために、今回はg++拡張の__attribute__((init_priority(N))を用いることにしました。
(参考ページ)

std::listのコンストラクタとTimerのコンストラクタの定義それぞれに、初期化順序を設定します。

std::list<uint32_t*> Timer::_counts __attribute__((init_priority(150));

Timer Hoge::timer __attribute__((init_priority(200));

これで、Timerのコンストラクタの呼び出し前に必ずstd::listのコンストラクタが呼び出されるようになり、実行時エラーが発生することはなくなりました。

--

[追記]
結局、初期化順序拡張を使わなければ解決できないようなコードは設計が悪いのだろうと思い、
初回使用時生成イディオムを使ってTimerクラスを書き直しました。

static std::list<uint32_t*>& _counts()
{
  static std::list<uint32_t*> counts;
  return counts;
}

今後は初期化を伴うような変数をstatic宣言することは避け、このイディオムを使うことにしようと思います。


0 件のコメント:

コメントを投稿