PPPUC++#10.5

どこまでやったか思い出すのに時間が・・・

8.5.8 Function call implementation
  • 関数呼び出しの詳細、何をやっているのか、です
  • 計算機の例の expression(), term(), primary()は相互呼び出しするので恰好の例
    • ただ、グローバルを使うと説明に困るので Token_stream&として参照を受け渡す事に変えたものでやります
  • で、コールスタックの話が延々と。。。
  • 面倒なのでエッセンスだけ
    • 関数が呼び出されると、引数、ローカル変数、関数が呼び出し元に戻るため「実装依存の情報」をパッキングしたデータ構造が生成される
      • function activation recordというそうな
      • 僕らにはスタックフレームの方が馴染み深い
    • 実装依存はその名の通り実装によって違う
    • 実はコンパイラ実装者から見ると 引数とローカル変数に差はほとんどない
    • 呼び出されるごとにこのデータ構造がどんどん詰まれる
      • 同じ関数であっても同様 → 再帰関数
    • 詰まれた activation recordは、関数から戻るときに消し飛ばされる。LIFOな感じ
      • やっぱcall stackと読んでくれたほうがしっくりくるかなー

8.6 Order of evaluation

  • コードはどういう順序で評価されるのか? という話
  • で、まずは変数、記憶領域についての順番
    • 定義が出てきたところで確保されて初期化
    • スコープの外に出たら破壊(再利用)
    • 大域変数はmain()に制御が行く前に、各翻訳単位で上から順番に初期化。寿命はプログラムが終わるまで。
      • じゃあ翻訳単位間の順番は? というと gccではリンクした順番になっているみたい。要するにELFの.ctorsの上から順になってるのだろうかと
    • ブロック内部も再帰的に同様の事がおこる
    • for()とかのstatement scopeも同様
  • 矛盾さえ起きなければ最適化はコンパイラの実装にまかされている。思いっきり最適化する場合もあり得るし、大抵はそうなると思う
8.6.1 Expression evaluation
  • C++の評価順序はどっちかというとプログラマ向けではなく、最適化の為に決定されている感じ。
    • わーしらなかったー(棒読み)
    • いわゆる「曖昧さを残す」ことで最適化の余地を残している、というやつ
  • 気をつけるべきなところは一点のみw
    • 1つの文で書き換えるのは1つにしぼる事!
  • 仕様の落とし穴に落ちるようなコードを書くと、他の環境(コンパイラ、マシン)に移った瞬間に幸せな事にまきこまれる

8.6.2 Global initialization

  • 大域空間は上から順
    • ってさっきも出てたような・・
  • ひとつ気をつけるべきなのがさっきも出てた翻訳単位を越えた初期化について


foo.cpp

  int x1 = 1;
  int y1 = x1 + 2;

  • は大丈夫だけど(いやいや・・・)


bar.cpp

  extern int y1;
  int y2 = y1 + 2;

  • とかやめろよ、とか。
    • いや,両方やめろw
  • 大域名がx1とかy1とかどこにでもありそうな名前で衝突してください、といっているようなもんなのが1つ目
  • また、y1, y2 どちらが先に初期化されるかで値が変わってしまうのが2つ目
  • 2つ目の方がどーしても必要な場合、その場合は 関数にパッキングすると良いというテクニック
    • たとえば、としてDateオブジェクトのデフォルト値が紹介されている
  const Date default_date(1970,1,1);
  • だと、ひょっとしたら使おうとしたときにまだ初期化されていないかもしれない
  const Date default_date()  // return the default Date
  {
    return Date(1970,1,1);
  }
  • とすべき。さらにはこのままだと毎回コンストラクタ走る(?正しい?)し、無駄なコピーの可能性があるので
  const Date& default_date()
  {
    static const Date dd(1970,1,1);
    return dd;
  }
  • するともっと良いとの話。
  • arguments about argument とか、、、Mr.Sって(ry

Namespaces

  • 結局のところブロックも関数もクラスも、根本的に何をしてるかというと、名前をグルーピングしているだけ
    • !!!なるほど
  • つまりは
    1. 衝突回避可能にする
    2. 参照可能にする

たしかに。

    • おもしろいなー
  • しかし今度はこのブロックとか*1関数とかクラスとかが衝突しちゃうジャン
  • 人名といっしょ。「はてな 太郎」とフルネームで参照するか「太郎」だけでおkかという。太郎だとどの太郎かわかんない
    • 実際 :: を使って NameSpace::Entityみたいにした完全名をFull Qualified Nameと呼ぶのです。
    • ちなみに名前空間の実装というのは結構単純で、本質的にはシンボル名の前に名前空間名をつけたものをシンボルテーブルに登録しているだけ
      • たとえば gccとかだとPascalticな_ZN<長さ>名前空間名<長さ>シンボル名Eというシンボルに変換される(こういうのはmangling nameとか呼ばれていて c++filtを通すと decryptされる)
namespace Name {
    int somevariable = 100;
    int anothervariable = 200;
}

はこうなる。

        .type   _ZN4Name12somevariableE, @object
        .size   _ZN4Name12somevariableE, 4
        .type   _ZN4Name15anothervariableE, @object
        .size   _ZN4Name15anothervariableE, 4
      • なんだそりゃ簡単! じゃあ前方参照はclass宣言だけで良いんでは!? となるんだけど、そこは腐ってもC++。namespace宣言があるまではシンボルテーブルが作れません。
      • 実体化のためには細かいサイズが必要なのですね。それを保持するシンボルテーブルが生成されて無い以上、コンパイラ的には「とりあえず名前はわかるしー」と許すわけにはいかないのです.
8.7.1 using declarations and using directives
  • しかし毎回毎回フルネームで呼ぶのはうっとおしい。
  • その場に太郎が一人しかいないってわかってるなら楽させてよ
    • というのが using です。
    • もっとも単純な使い方は using ; で Local Name太郎だけでよべるようになります
  • もっと怠惰なら、「とりあえず他にイナカッタラはてなさんとこの子ってことだよ」という宣言もできます。
    • それが using namespace ;
      • ちなみに怠惰な僕のvimC++のftpluginに iab un using namespace がはいってます
    • 使いすぎに注意。下手すると群雄割拠する戦国時代に戻ってしまいます。どれがどれやねん状態。
      • グローバル衝突はコンパイラが補足できるだけましかも。
      • あ、ひょっとしてそういう警告もあるかな? 調べてません
    • 何事も適当な分量で。
  • この本ではstd以外では un しません。あしからず

*1:ブロックは衝突しようがありませんね。名前無いから