寺田寅彦
科学者とあたまを書いたひとだけれども、この名前どっかで聞いた事があるなと思ったら, 昨日劇団青年座の人の講演で聞いたのだった。
「フユヒコ」という劇団青年座の公演が明日NHKで放送されるらしいです。冬彦ってのは寺田寅彦のペンネームらしい。
http://cgi4.nhk.or.jp/hensei/program/p.cgi?area=001&date=2008-12-12&ch=31&eid=9950
おもしろそうだから観てみようかな。
ところで小柴先生が同じような事を言っていたのを思い出した。このインタビューは面白い。
一流の理論家は、「自分の理論では、ここまでは使えるけれど、これから先は分からない」という適用限界をいつも意識している。ところが二流の理論家というのは、自分が名前を覚えた、あるいは、使った理論で何でもやれると思ってしまう。二流の理論家に困らされるのは、新しい実験計画が出てきたら、自分の理論じゃあそれは料理できないから、こんなものはつまらんと言ってはじいちゃうところだね。
東京大学理学部2006-2007 - 東京大学 大学院理学系研究科・理学部
(中略)
二流の理論家というのは、物事を全部知っていると思っているから、かえって害を及ぼすことが多いんだ。
参照とポインタの違い
参照とポインタの機能面の違いというのは良く知られているかと思いますが,コード生成の違いに関しては結構知らない方が多いのかもと思い解説します。
記事が大変長くなってしまいましたが, 個人的には参照は非常に面白い物だと思っていましてそれが伝わればと思います。もし何か間違いがあれば誰か教えて下さい。
まず, 参照とはつまりエイリアス(別名)だというのは良いかと思いますが,それをどう実現するかに関して規格は一切言及していないんですね。どうやるかはコンパイラの自由です。
具体的にどういう実現方法があるかというと, unionやハードウェア固有の機能の利用(特殊なメモリとか)もありえますが,現実的には
- ポインタ
- 名前置換 (分かり易く言うと #define x y みたいなただの置換)
がほとんどだと思います。
C++の参照は再代入不可, アドレス取得不可等の制約があるため簡単な解析で後者にも使えるというのがポイントで, Javaなどの参照とは異なっています。
そして, コンパイラはポインタなんて極力使いたくないので, 可能な場合は置換で参照を処理します。
では, どういう場合にポインタを使用するかというと他の関数の引数として渡す場合等, そうせざるを得ない場合です。例えば
void pair_swap(std::pair<int, int> &p) { int t = p.first; p.first = p.second; p.second = t; }
と
void pair_swap(std::pair<int, int> *p) { int t = p->first; p->first = p->second; p->second = t; }
は通常はほぼ同じアセンブリになると思います。
次にこれらがインライン化された場合を考えてみます。
参照の場合
std::pair<int,int> x = std::make_pair(0, 1); pair_swap(x); std::cout << x.first << x.second << std::endl;
が
std::pair<int,int> x = std::make_pair(0, 1); std::pair<int,int> &p = x; int t = p.first; p.first = p.second; p.second = t; std::cout << x.first << x.second << std::endl;
になります。すると,もはやpがポインタである必要がないので,ただの置換で処理します。
std::pair<int,int> x = std::make_pair(0, 1); int t = x.first; x.first = x.second; x.second = t; std::cout << x.first << x.second << std::endl;
次に, 全てのアクセスがx.firstかx.secondの形なので, xに連続領域を割り当てる必要がない事が解析され,pairはバラバラに分解されます。
int x_first = 0; int x_second = 1; int t = x_first; x_first = x_second; x_second = t; std::cout << x_first << x_second << std::endl;
最終的に定数畳み込みで
std::cout << 1 << 0 << std::endl;
になります。こんな感じで参照を利用すると, 他のコード変換の効果が向上する場合が結構あります。
ポインタの場合
インライン化すると
std::pair<int,int> x = std::make_pair(0, 1); std::pair<int,int> *p = &x; int t = p->first; p->first = p->second; p->second = t; std::cout << x.first << x.second << std::endl;
となります。さて, これの最適化は困難です。pは再代入などが可能なので, ただの置換では処理できません。より最適化をかける為にはエイリアス解析という高度な解析が必要になります。
g++の場合は, この程度の単純なコードであれば-O2で両者とも全く同じコードになりますが, コンパイラの苦労度合は全然違うんですね。
エイリアス解析は完全に行う事が困難なので,分岐が入った複雑なコードになると-O2でも差が現れてくると思います。
まとめですが, 参照は可能な場合は単なる置換で処理されるので生成されるコードも変わってきます。もっと面白い事をしているコンパイラもあるかもしれません。
ここに書いたのは参照の実現の一形態であって, 処理系によって異なるということに注意しておきます。少なくともg++では以上のような感じになっているようです。[追記]これは調べなおします。