一時的な型 長谷川啓 2018.11.01
一時的なタグ型 void f(struct S { int a; } s) { } ※ struct T { double d; }; struct S* ps; struct T* pt; ... } ※ void g(struct S { int a; } s) struct S s2 = { 1 }; f(s2); /* エラー。f::パラメータスコープ::struct S と g::パラメータスコープ::struct S は compatible ではないから。*/ void f(); // グローバルな f の宣言を隠す f(s2); // ok
一時的なタグ型(続き) 前ページの※のポイントで f::ボディ f::パラメータスコープ は破棄されるが、 型 f::パラメータスコープ::struct S 型 f::ボディ::struct T はどうするべき? ⇒ 型 f::パラメータスコープ::struct S は破棄されてはならない。 f がグローバルスコープにあるから。 前ページのような函数 g における f の呼び出しで型 f::パラメータスコープ::struct S が参照されうるから。 型 f::ボディ::struct T は※以降参照されることがないから、メモリの使用の観点から、破棄されるべき。 但し、必ずしも破棄しなくてもよい。 破棄するのならば、f のボディで作成された struct T* も破棄される必要がある。
scope, tag, record_type, incomplete_tagged_type tagn incomplete_tagged_typen record_typen
~scope() でタグを破棄しないようにすると・・・ struct scope { map<string, vector<usr*> > m_usrs; // 変数、函数等 map<string, tag*> m_tags; // タグ ... ~scope() { for (auto u : m_usrs) for (auto p : u.second) delete p; /* 分かりにくいが、 for (auto p : m_tags) delete p.second; のようにしない。 */ } }; struct tag { enum kind_t { STRUCT, UNION, ENUM } m_kind; scope* m_scope; /* タグが所属する scope を保持しておく。しかし、scope が破棄されても tag が破棄されないとすると、破棄されない tag は破棄された scope を参照しうることになる。これは、分かりにくいし、マズい。 */ class record_type : public type { tag* m_tag; ... }; class incomplete_tagged_type : public type { tag* m_tag; ...; ~incomplete_tagged_type(){ delete m_tag; }; // ここでタグを破棄する
~scope() でタグを破棄しないようにする改訂版 struct scope { map<string, vector<usr*> > m_usrs; // 変数、函数等 map<string, tag*> m_tags; // タグ ... ~scope() { for (auto u : m_usrs) for (auto p : u.second) delete p; for (auto p : m_tags) p.second->m_scope = 0; // こうする } };
variable length array (VLA) void f(int n, int (*p)[n-3]) { int a[n+5], b[n*2]; int (*pa)[] = &a, (*pb)[n+10] = &b; ... } ※ ※のポイントで p の指す VLA の次元 n-3 a の次元 n+5 b の次元 n*2 pb の指す VLA の次元 n+10 の演算結果は破棄される。 class varray_type : public type { const type* m_T; var* m_dim; varray_type(const type* T, var* dim) : m_T(T), m_dim(dim) {} }; のような実装だとすると、varray_type はいつ破棄されるべきか? varray_type::m_dim は型式の dag の設計からすると必要なメンバなのだが・・・
VLA(続き) ブロックスコープの VLA パラメータスコープの VLA varray_type::dim はそのスコープが破棄されるときに同時に破棄されるので、同時に varray_type オブジェクトも破棄する。 パラメータスコープの VLA ブロックスコープの varray_type::dim と同じ扱いだが、パラメータスコープの破棄のタイミングで不完全型配列に変更する。 例えば、前ページの f の第2引数の型は pointer(array(int, 0)) に変更する。
dag による型式の例 function pointer function int void void (*signal(int, void (*)(int)))(int) の型式 function 等価な型には同じ型オブジェクト を割り当てる ⇒ 型の等価性は型オブジェクトの アドレス比較で判定できる pointer function int void
dag による型式の実装 例 class pointer_type : public type { public: }; const type* m_T; pointer_type(const type* T) : m_T(T) {} // private コンストラクタ ... public: static const pointer_type* create(const type* T) { table_t& table = T->tmp() ? tmp_table : pmt_table; table_t::const_iterator p = table.find(T); if (p != table.end()) return p->second; return table[T] = new pointer_type(T); } }; tmp_table は函数のコンパイルが終わったら、一旦クリアする。
inline 函数内の一時的な型 inline 函数の情報 3番地コード パラメータスコープ およびその子供のブロックスコープ inline 函数内で生成された一時的な型 ★これ! inline 展開する前に一時的な型を破棄すると当然だがまずい。
ブロックスコープ内の函数宣言が例えば, VLA へのポインタを引数にとる void f() { ...; int g(int n, float (*p)[n * (n + 1)]); ...; } g の宣言に対する 3 番地コードは生成されてはならない g の第 2 引数の型は float の不完全型配列へのポインタにする g の引数の型の一部に VLA を入れてはならない 備考 函数の外側の函数宣言が VLA へのポインタを引数にとる場合 VLA へのポインタは不完全型配列へのポインタに変更する 生成された 3 番地コードは破棄する 函数定義が VLA へのポインタを引数にとる場合 VLA へのポインタはコード生成が終わった後、不完全型配列へのポインタに変更される 生成された 3 番地コードのオペランド ( x := y op z の x, y, z) は函数のブロックスコープに引っ越しされて、函数の最初のコードで通常通り評価される
もう少し複雑な例 void f(int n) { void (*array[n*3])(int m, int (*pa)[m+2]); ... } array の型は函数ポインタの VLA. この函数の第2引数は VLA へのポインタ array のバイト数を計算する3番地コードは生成されなくてはならない pa の指す VLA のバイト数を計算する3番地コードは削除されなくてはならない
ブロックスコープ内の函数宣言が例えば, VLA へのポインタを引数にとる(2) 函数定義の場合と同様に、3番地コードを出してオペランドを移動するのはだめ 例 void f(int n){ ...; void g(int n, int (*)[n+2]); ...; } t0 := n + 2 t1 := t0 << 2 が生成される. t0 や t1 を移動することはできるが, n は移動できない。 n は g のパラメータスコープが破棄されるときに、同時に破棄される.
ブロックスコープ内の函数宣言が例えば, VLA へのポインタを引数にとる(3) 実装の例 ブロックスコープ内のパラメータスコープで VLA が宣言されたら不完全型配列にすぐに変更する ブロックスコープ以外のパラメータスコープで VLA が宣言されたら通常通り