真のソフトウェアエンジニアに必要なモノ(SQLは銀の弾か?)


件の社長ですが、ブログで、まとめ記事を出しております。あれだけ口悪く私のことを煽っていたのにバツの悪い終わり方だと思いますが、まぁ、これ以上、『SQLはオブジェクト指向言語の数十倍の効率』の件を追求する必要もないのでその件は終わりにします。

ただ、私の方ですが、久しぶりに火がついたので気ままに書いてみます。もっとも粘着質といわれるのも嫌なので、件のブログ自体にはコメントしません。が別に書いていることが正しいとも思っていません。件のブログですが、コメント欄が消されていますので、疑問等ある人はこちらのコメント欄にでも書いて頂ければ『私に答えられること』でしたらコメントします。

素直さは如何に大切か、とか、騙されないようにする為に(適切な議論の方法)と記事を書いてきましたが、そもそも論として、ソフトウェア開発に関連した技術面で何か記事を書くのであれば、それは技術者を代表してという立場で発言することになるでしょう。ここでの技術者の代表とは、『ソフトウェア開発をリードし設計、開発、テスト、トラブルシュートを行い、単なる知識だけでなく頼れる技術を持った、顧客だけでなく共に働く人や競合他社からも一目置かれるような人』ということで話をします。お前はどうやねんとツッコミが来そうですが、私は僭越ながらその末席において頂いていると思っております。
というわけで、以下、真のソフトウェアエンジニアとって必要なモノについて語ってみましょう。

『銀の弾などない』ことを理解する

恐らくソフトウェアエンジニアだけでなく、顧客の情報システム部の方や、現代では企業経営者全ての人に教養としてお薦め本に『人月の神話』があります。私はざっと一読しまいたが、ソフトウェア開発の真実をみることができるでしょう。ちなみに訳本のせいか私にとっては少々読みにくい(というかくどい)です。
その中にある、『銀の弾などない』という章で著者のフレデリック・ブルックス氏は『ソフトウェア開発においては○○を使えば生産性や信頼性、安全性が著しく向上したりするという特効薬は存在しない』という趣旨のことを主張しています。この主張は約25年前になされましたが、今でも充分に通用するでしょう。つまり、「○○を使えば生産性がX倍向上します。」ということを耳にするかと思いますが、『そんなものはない』というのが氏の主張で、私も自身の体験からこの主張は現在のところ正しいと思っています。
ひょっとしたら経験は浅いが技術力のある方の中には「そんなことはない、銀の弾はある!」と思うかもしれません。一経験者から言わせてもらうと恐らくそれは思い込みです。
ただ、この主張が将来に渡っても正しいかどうかは解りません。なぜかというとこの主張は現在のソフトウェア開発の弱点とそれが将来に渡って解消されないという予測を行っているだけだからです。人間という生き物は欲深いものでこの弱点を克服しようと多くの人が日々努力しています。私がADPを開発しているのもまぁそういう努力の1つです(と言っておきましょう)。
当然ですがフレデリック・ブルックス氏もその点はきちんとフォローされており、約25年前に、銀の弾の候補の一つとしてオブジェクト指向プログラミング(OOP)を挙げておられます。ただ、その約10年後に出された『「銀の弾などない」再発射』ではオブジェクト指向が本来使われるべき領域(業務ロジックにオブジェクト指向の適用)ではなく低レベルな部品レベル(リストクラスやGUI等)にとどまっていると指摘しています。
現在ですが残念ながら状況はあまり変わっていないでしょう。オブジェクト指向はだいぶ浸透してきたかと思いますが、現在ではソフトウェア開発期間に求められる時間が短くなってきています。つまり顧客はカジュアルにサービスを立ち上げたがります。そうなるとじっくりと開発するというわけではなくありものでちょちょっと作ることになり、OOPといっても『ライブラリを使う』ということはあっても業務ロジックを作ることはそう多くはないでしょう。また何よりOOPを使った失敗プロジェクトも多かったのも事実です。

そして、件の社長ですが、『少なくともRDBを使う業務アプリの開発において、データを操作する部分に関してはSQLは銀の弾になりうるのではないか』と主張しているのかと想像します。

行間を読む

 ソフトウェアエンジニアは普段コンピュータばかりを相手にしているのでどうしても理屈っぽくなり、言葉を額面どおりに取る傾向にあります。がソフトウェアエンジニアたるものコンピュータばかりでなく人間も相手にしなければなりません。行間を読むことは人間同士のコミュニケーションで重要なことです。がもちろんですが相手に行間を読んでもらう前提で話をしてはいけません。コミュニケーション能力は技術者というより人間としての経験値が必要ですが、幸い今ではインターネットを使ったコミュニティが豊富にあります。行間を読む訓練は昔より遥かに簡単にできるでしょう。

SQLは銀の弾になりえるか?

という訳で、まとめ記事ありました、件の社長が本当に言いたかったことを推測してみますと、
『少なくともRDBを使う業務アプリの開発において、データを操作する部分に関してはSQLは銀の弾になりうるのではないか? 銀の弾ではなかったとしてもオブジェクト指向プログラムより使える技術だろう』
ということが某社長が言いたかったのかと仮定します。
で『その根拠』について私の経験を元に考えてみましょう。ちなみに、『人月の神話』にはSQLは銀の弾の候補に上がっていませんでした(間違っていたらコメント欄にご指摘下さい)。

この点については社長自信、色々上げておられるのですが、私の経験上一番納得できるポイントは

・オブジェクト指向技術を使ったプロジェクトが失敗したときの被害

・SQLに頼ったプロジェクトが失敗したときの被害
とを比較した場合、どちらがより被害が大きくなるか?です。

件の社長が言いたいのは(というか過去の議論で私が理解できた社長の主張は)『OOPを使ったプロジェクトが破綻した場合の方がより被害が大きい。』ということでした。私の中では『どっちもどっち』と思う面もなくはないですが、確かに実際に聞く話は『OOPを使ったプロジェクトの破綻』が多いし深刻度が高いです。もっとも私の周り3メートルの範囲ですが。

この事実は、単純に『OOPが銀の弾候補』になり、それ以外にもオブジェクト指向がバズワードとして色々宣伝されたので、多くのチャレンジャーが集まり、壮大な実験の結果、失敗例が集まったということかと思われます。

その他、OOPが思ったほど実力がないということの例を挙げますと統合開発環境の存在があるでしょう。本当にOOPが銀の弾なら統合開発環境は要らないでしょう。皮肉な話ですが統合開発環境が高機能になればなるほどOOPがそれ程たいしたことではないという風に聞こえてしまいます。例えば、「統合開発環境のリファクタ機能を使えばそんなの簡単です。」という話を聞きますと、すごいのは統合開発環境であって言語ではないということでは?と疑問が出てきます。ちなみに「統合開発環境がないと開発できない」とか言われると『本末転倒やん』と嘆きたくなります。

さらに別の例ですが、私が関わったプロジェクトでC++を使ったものがあったのですが、バグ入りのものをリリースしたが、既に担当者がいなくなったので修正が出来ないということで私に助けを求めたものがありました。単純にOOPで作られたというだけでなく、マルチスレッドで動作していたのでバグの原因が不明で誰も手が付けられなかったということでした。OOPの思想の1つにカプセル化(隠蔽、ブラックボックス化)があります。確かにバグがないプログラムを再利用する場合はカプセル化は理想的です。しかし、そのカプセルの中にバグがある場合は否応なく内部を調べる必要があり、これにプラスして経済的な制約が加わると大変なことになることは想像できるでしょう(まぁ私は儲かるのですが・・・ちなみにこういうことでお困りの方がおられたら私ならなんとかできるかもしれません)。

ここまで言いますと『じゃなんでお前はC++を使っているんだ』とツッコまれそうですが、道具はあくまでも道具で、適切に使えば良いという話だけです。長所、短所を理解した上で使えばよいでしょう。ちなみに私が単独でプログラムを記述する場合はC++をメインで使いますが(もっとも最近はADPがメインですが)、誰かに引き継ぐ前提のプログラムの場合、関わるメンバを見て言語を選択します。そして、私の半径3メートル以内ではC++を使うことは残念ですがあまりなかったです。

一方でSQLに関してですが、さすがに長年の実績がある言語で、例えばパフォーマンス上で問題が発生した場合、様々な解決策(ノウハウ)があります。また私の半径3メール以内でも多くの人がSQLを使っています。SQLが出来ない人は少ないです。最近では、あるSQLが遅くて色々試行錯誤していましたら、一緒に働いている方から「何か知らんがこうすれば速くなった」とアドバイスを受け実際に速くなったケースがあります。大人の事情で具体的な詳細は明かせませんが、そういうことは皆様の回りでも現実に多々あるでしょう。こういうことを言うと「実行プランをきちんと解析せいや」とかお叱りを受けますが当然そんなこともしております。
もちろん、SQLをどうこねくり回しても解決できないこともありますが、その場合でも案外ベタな解決方法(私のブログで紹介しているようにJOIN崩しとか、その他非正規化とか)があり、この辺りに関しては知る人ぞ知るという感じで、私の回りでは結構あるあるネタになっています。
このような言語自体の性質および実績から来る信頼性は追い込まれたエンジニアにとってはありがたい存在で、「SQLは良い」という意見については反対する気はないです。ただ、「SQLが効率的」といわれると普段から遅いSQLを速くするという作業も行っている身としては?と思うわけです。
またMDXという言語を知ってからは、JOINしながら集計することに関してはMDX(OLAP)の方がSQL(RDB)より強力という認識でおります。道具はやっぱり適材適所で過信はいけません。

業務アプリ限定で言いますと、SQLとOO言語を混ぜて使う必要があるために、SQLとOO言語のパラダイムの差を吸収する必要があるでしょう。いわゆるインピーダンスのミスマッチで、その解消策の1つとして『OO言語で統一する』という試みが昔から行われています。古くはオブジェクト指向DBなどで、今ではO/Rマッパーなんかですね。ここで某社長の思いの行間を読むと「OO言語に統一できるという考えがファンタジーだ!」ということで、その根拠にN+1問題をあげておられます。ちなみに「SQLはオブジェクト指向言語の数十倍の効率」自体は間違った主張ですが、その行間を言い直すとN+1問題ということになります。N+1問題はググれば出てきますし、私がSQLの実行パフォーマンスについて 2010で指摘した実験2は原理的にN+1問題と同じになります。
そして社長は「オブジェクト指向側に寄せるより混ぜて使うことを前提に上手く開発できるようなスタイルを確立すべし」ということが言いたいのでしょう。まぁ適材適所ですね。
ちなみに『OO言語とSQL(リレーショナルモデル)とのパラダイムの差を吸収することは難しいが、述語理論とSQL(リレーショナルモデル)との差はあまりないのでSQLを呼び出す言語を述語理論に基づいた言語にすれば上手くいくのでは』というのが私の仮説になります。
もう1つうんちく次いでに語りますと、「SQL系の言語で統一する」という試みもありました。4GLとか言っていたものです。これは早々に消えたかと思います。4GLが宣伝されていたとき、私はあまり関わっていませんでしたが「SQLでGUI」と言われても『どう組むんや!』ということは明白で、まぁやっぱり道具は適材適所なのでしょう。

「オブジェクト指向プログラミングが銀の弾かどうか?」というのはまだ結論が出ていないかと思いますが、最近では関数型の言語が一部ではクローズアップされています。ちなみにADPがベースにしているPrologという言語は述語理論を基礎においています。私は述語理論を押しているわけです。先のことはわかりませんが、オブジェクト指向プログラミングが昔の構造化プログラムと同じ道を行き、将来別のパラダイムがスタンダードになっているかもしれません。もちろんRDBが駆逐されているという世界もあるかもしれません。つまらない意見ですが、まぁ先のことはわかりません。

そして「SQLは銀の弾か?」と言われると『データを扱う上では候補ぐらいにはあげてもよいけどSQLはそもそもドメイン固有言語ではなかったですか?』。というのが私の意見になります。

以上、行間を読んで書いてみましたが、如何でしたでしょうか? がんばりましたがさすがにSQLを持ち上げるのは厳しいです。つまらない結果ですがまぁ現実はこんなもんです。

最後に1つだけコメントしますと、このように書けば炎上はしませんが、多くの共感は得られるのではと思うのですが、まとめ記事では残念なことに主張すること自体を取り下げたようです。
いったい、彼は何が言いたかったのでしょうか?

2011-08-18 | コメント:2件



騙されないようにする為に(適切な議論の方法)


素直さは如何に大切かという記事を書いたのですが、考えてみれば『それは私自身にも当てはまるのでは?』というツッコミがきそうですし、何よりこれはエンジニア向けに書いているので『判別つかない議論をどうすれば見極めれるのか?』とか『自身がどういう姿勢で臨めばよいのか?』という全うな質問もあるかと思います。
そこで、ITエンジニアが適切に議論(主張)を行う方法をメモしてみます。

1.自分の主張が正しことの裏を取る
もっとも大事なことは自分できちんと根拠を示すことです。では根拠となるのはどういったものでしょうか?
 (1) 実験する
  例えば、『○○の方が速い』ということでしたら実際に実験してみればよいのです。実験というのは何より客観的ですのでその結果は事実として受け止める必要があるでしょう。ただ、実験では『たまたま自分のマシンでは速かった』ということもあるでしょう。したがってその実験が再現できるよう環境もあわせて提示しましょう。
SQLの実行パフォーマンスについて 2010ではこの方針にのっとって主張を行っています。

 (2) 論理的な考察
  実験にプラスしてそれを補う理論的な考察も必要でしょう。ここで重要なことは、IT周りの議論が起こったとき理論的な考察というのに無理がある場合があることです。つまりITの世界は数学のように割り切れない面もあります。パフォーマンスという一見簡単な問題にしても純粋な数学からすれば充分に複雑です。いわゆる複雑系というやつです。それ以外でも例えば、工数が少なくなるというのはそもそも主観が入るでしょう。そのような場合は出来るだけ問題を簡単にして説明可能な実験に分割してそれぞれ実験を行えばよいでしょう。
[ADP開発日誌]SQL(JOIN)の実行パフォーマンスについて2011ではこの方針にのっとって主張を行っています。

 (3) 他のソースの引用
  確からしい他のソースから引用するのももちろん良いでしょう。その場合引用元を開示すれば他の人が検証可能となります。この場合注意したいのはインターネットの情報はウソもあるということです。騙されないようにする必要があるでしょう。

2.騙されない為のテクニック
ITエンジニアたるもの技術的な論争についてはきちんと処理をしたいところです。口頭での議論だとついつい煙に巻かれることもありますが、ネット上ですと文章として残っていますので冷静に対応すればよろしいかと思います。
 (1) 発言者の過去の発言を検索し矛盾がないか検証する
  残念ながら論理的に筋道が通った話ができない人がいます。また人というのは時間と共に考え方が変わります。相手の発言はそのようなことを考慮しているかどうかを検証する必要があります。その際にあらかじめ公開の場で質問をしておくと後々それが助けになります。

件の社長は、炎上したのでまとめ で、
 『SQLはオブジェクト指向言語の数十倍の効率』
という発言し、その根拠にコメント欄で、JOINの例を挙げたが、その一年後にツイッターで、
『JOINをなくすならAPサーバでキャッシュする。 集計関数をなくすなら、ORDER BYを禁止する。 SQLがイヤならRDBMSは使わないこと。 何度も書いているけれど、SQLで出来ることをAPサーバでやっても、【絶対に】DBサーバの負荷は減らない。 ただし、下手糞を除く。』
と発言しています。そしてそれを指摘すると今度は、シビアな設計で、
『JOINするしないでは、トレードオフの関係は、すべてのリソースに対して技術者の技量しかありません。』
といっています。発言に矛盾があることはもうお分かりでしょう。

 (2) 質問に答えない
  少なくとも明らかな素人を除いて、質問に答えない(そして中傷してくる)相手はその質問自体に答えたくないのでしょう。
SQLの実行パフォーマンスについて 2010で私は件の社長本人に『SQLはオブジェクト指向言語の数十倍の効率』の考えは変わったのか質問しましたが、件の社長はそれには直接答えていません。逆に質問してきたり、独自の認定を行い、議論を終わらせたりします。少なくとも自己の主張が正しいとするならば質問には答えられるはずです。

 (3) 綺麗な図に惑わされない
  これは一部の方に行われているが綺麗な図や表を用いてさも正しいという風に見せ付けることがあります。いくら綺麗にまとまっていても真のITエンジニアたる者だまされてはいけません。よーく検証しましょう。

例えば、以下の図ですが、

一見綺麗にまとまっているでしょう。しかし、データ転送量の欄を見てみましょう。
『データ転送量  トラフィックに影響  JOINした方が少ない 』
とあります。これが成立しないことは、[ADP開発日誌]SQL(JOIN)の実行パフォーマンスについて2011で指摘しています。一見すると綺麗な表でまとめられているので気がつかないかもしれませんが、きちんとみれば分かります。また他の項目についてもきちんと説明せずに結果だけが表になっていることが分かります。

3.議論をする上での心構え
本来議論というのは、炎上をねらったり、闇雲に自己主張をするのではく、自己の見聞を広めたり、起こった事象に対して深い考察を行ったりするものであると考えます。

 (1) 議論は勝ち負けではない
  これがぜんぜん解っていない人がいます。議論で相手を言い負かすとか論破するとかは少なくともITの分野では意味がありません。

 (2) 相手の主張に耳を傾ける
  自己の主張に反論してくる人は、ある意味ありがたい人です。相手の主張を受け入れ真摯に答えましょう。私は、SQLの実行パフォーマンスについて 2010で、「OO言語側の最適化が不十分である可能性がある」と結論付けていますが、これはつまり一見すると『SQLはオブジェクト指向言語の数十倍の効率』ということが正しいと思える場面が存在することを認めています(その上でそれは視野の狭い経験にしか基づかないという指摘を行っています)。

 (3) 相手に感謝する、発言を憎んで人を憎まず
  議論を行った後は相手に感謝の意を表する必要があります。私自身、SQLの実行パフォーマンスについて 2010という記事を書くにあたって再度勉強になりました。そういう意味では件の社長には感謝したいですが、それを『文盲のサル』と言われてしまっては腹も立ちますがそのような感情は捨てて、この場で感謝の意を表明しておきましょう。

2011-08-15 | コメント:3件



[ADP開発日誌-公開1周年記念特集 Part6] プログラミング言語の制御構造のいろいろ(4)

ちょっと余計な記事が入りましたが、続きを

C++の仮想関数の欠点

 話が少し前後しますが、Part4の記事でC++の仮想関数呼び出しの仕組みについて説明しましが、ここではC++の仮想関数の欠点について指摘します。C++ではvtableというメンバ関数のアドレスを集めたテーブルを用いて仮想関数の呼び出しを実現していました。この方式は効率がよいのですが『コンパイル時に呼び出すべき仮想関数が決定しなければならない』という弱点があります。
どういうことかといいますとC++でのメンバ関数呼び出し

 object.virtual_method( arg1, arg2, arg3)

という呼び出しで、virtual_methodというメンバ関数名はコンパイル時に参照されますが、実行時には内部的に振られた番号(vtableのインデックス)になります。つまり実行時にはこの名前は参照できません。と同時にvtableのインデックスを取得する手段もないので、実行時に呼び出すメンバ関数を選択したいということができません。
これの何が欠点かピンとこないかもしれませんが、例えば、バッチファイルからVBScriptを使ってExcelを操ったりしますが、この特にExcelのバージョンをあまり気にせずにExcelを操作(メソッドを呼び出す)するでしょう。これと同じことは、C++の仮想関数の仕組みではストレートに実装できないということです。Windowsでは皆さんご存知のとおり、COMという仕組みをOSに実装することで実行時に呼び出すメソッドを特定することを行っています。
COMというとえらく古いと思われるかもしれませんが、.NET Framkework からExcelを呼び出す場合もCOM相互運用性という仕組みを使って.NET Framework → COM → Excel という風に呼び出しいます。
話が脱線しますが、私は.NET Frameworkが廃れるのではないか? と思っていますが、その理由のひとつが .NET FrameworkがCOMやOLE DB等のようにWindows APIを充分に置き換えていないと思えるところにあります(もっとも先のことは解りませんのでなんともいえませんが)。

関数の動的なロード&実行の例

  場合によって呼び出す関数を変える
というプログラミングテクニックは、オブジェクト指向プログラミング以外にもあります。典型的な例のひとつにデバイスドライバがあります。
デバイスドライバはご存知のとおりハードウェアとOSのAPIを橋渡しするソフトウェアでハードウェアに合わせて作成されています。ハードウェアを変えるとそれにあわせてデバイスドライバも変えます。
デバイスドライバはCで記述されることが多いです。最近のOSではPlag&Playが一般的になりましたし、USB接続機器ではOSを再起動せずに、デバイスドライバがロードされます。このような動的なソフトウェアのロードの仕組みはどうなっているのでしょうか?

続いては、公開1周年記念特集記事として『プログラミング言語の制御構造のいろいろ(5)』を書いてみます。

2011-08-14 | コメント:0件



素直さは如何に大切か


ちょっとADPの記念記事からは外れますが思ったことがありますので記事にしてみます。

技術的な論争を行ったときにどうしても、ご自身の主張(例えば『SQLはオブジェクト指向言語の数十倍の効率』)が否定されたことを認めたくないばかりに、『サルに何を言っても意味がないので、次に来ても消します。』等と言って自説を強弁したり相手を中傷したりすることがあります。
ちなみに、ここで私を文盲のサル扱いしていますが、そういうこと自体が褒められたことではないし見る人がみれば議論から逃げているとして取られないかと思います。

私も含めて人間誰しもそういう面があります。ので、そんなことをいちいち追求しても仕方がないことだと思っていましたし、それを追求すること自体あまり程度のよいものではないと思っている面もありました。

要するに零細企業の一社長が何をいっても社会にはあまり影響がないので言わせておけと思っていましたが、私はこの『事実より自分の主張を優先する』という性が少なくとも日本人の特性として思った以上に根ざしており、零細企業の一社長だけでなくそれなりの企業や組織にもそういうことがあるのではないかと心配しております。

私はこの議論を行っているなかで、『仮にこれが今の原発事故の話ならどうだろう』と思って寒いものを感じました。

つまり、原発事故で某電力会社は事故から2ヶ月後にメルトダウンしたことを認めています。記事
最終的に某電力会社はメルトダウンを認めたから事故の重大さが周知され、我々国民も対応について選択が出来る(または覚悟が出来る)ようになったかと思います。当初はメルトダウンはないといっていたり、発表が2ヶ月後になったということを考えると充分ではないことも明白で、ここでも事実を素直に受け入れることの重要性がわかります。

また、他の電力会社のやらせメールもありましたが、これに関しても『技術的には取り扱いを慎重に行わなければならないのに一方の考え方を押し通す為に、一見技術的には正当とみえるような議論を行う。つまり煙に巻く。』ということで、そう多くないとしても私の回りでもしばしば見受けられる現象です。

ここで重要な点は、技術的な内容についての真偽は技術者しか分からないということで、もし議論する一方が事実をありのまま受け入れずにそれを隠したりねじ曲げたりすると場合によっては大変な過ちを生み出すことになるということです。つまり議論を見ている専門知識を持たない観衆は判断がつかないところで勝手に誤った方向に持っていかれるということです。

私も過去に議論に巻き込まれて事実を受け入れることから逃げたくなったり逃げたこともありますし、やり込められると徹底的に逃げる人もみてきました。さらに裏で手を回して私を追い落とそうとした人もいます。
今回はただの議論なので『まぁいいか』ですみますがそれでも『SQLはオブジェクト指向言語の数十倍の効率』という間違った知識を若い人に伝えたくないのでがんばりましたが、私は今回のことは改めて如何に技術者として特に重要な技術的な内容については正直でなければならないと思い知らされました。

私も頭の体操ということで議論していまし今後も議論を行うでしょうが、こういうことを肝に銘じて技術的な事実関係は真摯に受け止めるように勤めたいですし、相手がそういう態度に出るのなら厳しく追求したいと思います。と同時に権力者が同じように国民を煙に巻いてこの国を誤った方向に持っていこうとしないようにしっかりと見ていきたいと思います(まぁこれには限界があるのですが・・・)。

2011-08-11 | コメント:0件



[ADP開発日誌-公開1周年記念特集 Part4] プログラミング言語の制御構造のいろいろ(2)

前回からちょっと間が空いてしまいましたが、ADPの1周年記念記事のPart4です。

関数呼び出しのスタックの使われ方


前回の記事の終わりにスタックという言葉が出てきましたが、スタックとはプロセス(正確にはスレッド)毎に用意されているメモリエリアで、関数呼び出しやローカル変数の保持に使われます。

以下のC言語での関数呼び出し時のスタックの使われ方の例を図1に示します。
func( arg1, arg2, arg3); /* ------- ※1 */

         図1


スタックは伝統的にアドレスの上位(数字が大きい)から下位に向かって領域が確保されます。
※1の関数が呼び出されるとき、先ず引数がスタックに積まれ、次いでリターンアドレス、そしてローカル変数の領域が確保されます。関数というのはどこから呼び出されても元の場所に戻ることが出来ますが、それが実現できるのは、呼び出し後に実行すべき命令のアドレス(リターンアドレス)をスタックに保持しているからです。
また、同時にどこから呼び出されてもローカル変数が『関数内で一時的に有効な変数』として機能できるのもスタックに変数のエリアを確保しているからになります。

ちなみに、数年前に流行したセキュリティリスクでバッファオーバーランというものがありますが、これはローカル変数の領域を溢れさせアドレスの上位にある戻りアドレスを書き換えてウイルスのプログラムを実行しようというC言語の関数呼び出しの仕組みを悪用したものになります。現在ではCPUレベルでの対策(NXビットとかXDビットとか呼ばれものでデータ領域の実行の禁止)が行われ、バッファオーバーランの脆弱性が起こりにくくなっています。

スタックには引数が積まれていますが、引数が積まれる順番には2通りのやり方があります。図1ではリターンアドレスに次いで arg1,arg2,arg3 と積まれていますが、反対に arg3,arg2,arg1 というやり方もあります。arg3,arg2,arg1の順番ですが、一見すると反対に見えますが、スタックに積む順番はarg1,arg2,arg3となります。ややこしいですが、※1の擬似アセンブラコードを示すと意味が良く分かるかと思います。

	※2 ※1の擬似アセンブラコード(cdecl呼び出し)

	PUSH arg3
	PUSH arg2
	PUSH arg1
	CALL func

PUSH命令の発行順とスタック上のリターンアドレスから見た順番が反対になります。
関数の呼び出し方法(つまりどのように機械語に翻訳するか)を呼び出し規約(主にx86のCPUで用いられている表現)といい、※2のような呼び出し方法をcdeclと呼びます。呼び出し規約はその他にPASCAL(文字通りPASCALで採用されている)とかstdcall(Windows-APIで採用)とかthiscall(C++のメンバ関数呼び出し)等があります。

メンバ関数の呼び出しでのスタックの使われ方


続いて、C++のメンバ関数呼び出しでのスタックの使われ方について説明します。
以下のC++でのメンバ関数の呼び出し時のスタックの使われ方の例を図2に示します。
object.method( arg1, arg2, arg3); // ------- ※3

            図2



※3の擬似アセンブラコードを以下に示します。

	※4 ※3の擬似アセンブラコード(thiscall呼び出し)

	PUSH arg3
	PUSH arg2
	PUSH arg1
	PUSH object
	CALL method

違いは、object(正確にはobjectのアドレス)がthisポインタとして引数の一つとしてスタックに積まれていることです。その他の違いはありません。こうしてみるとオブジェクト指向というのは単純に

method( &object, arg1, arg2, arg3)

というコードを、

object.method( arg1, arg2, arg3)

という風に記述できる構文上の違いであるに過ぎないということに気づくかと思います。
ADPでは、この考え方を推し進めて、メソッド形式(メンバ関数呼び出しとほぼ同じ意味)として通常の述語形式での呼び出しとメソッドの呼び出しを混ぜて使うことができるようにしています。

ちなみに、私も含めて、多くのC言語の上級エンジニアがこのような見方をしてC言語からC++(オブジェクト指向)に移行していたかと思います。

もっとも、この話は、『仮想関数はどのように機械語に翻訳されるのか?』の話をしなければ終わりになりません。
次いで、仮想関数の呼び出しの話をします。

仮想関数の呼び出しでのスタックの使われ方

前節で説明したメンバ関数の呼び出しは従来の関数呼び出しの延長線上のものですが、ここでは、仮想関数と呼ばれるオブジェクト指向独特の呼び出し方法について説明します。ちなみに仮想関数の説明自体は省略します(コメント欄でリクエストを頂ければ記事を追加するかもしれません)。 仮想関数の説明は次の記事で行います。

以下の仮想関数の呼び出しについて考えます。ちなみにスタックの構成は図2で仮想関数・通常のメンバ関数(非仮想関数)での違いはありません。
object.virtual_method( arg1, arg2, arg3); // ------- ※5
※5の擬似C++コードを以下に示します。

	※6 ※5の擬似アセンブラコード(thiscall呼び出し)

	PUSH arg3
	PUSH arg2
	PUSH arg1
	PUSH object
	MOV	 EAX, [object + vptr] ; ------------------- A
	MOV	 EDX, [EAX + virtual_method_offset] ; ----- B
	CALL EDX ; ------------------------------------ C

object + vptrなどや、EAX + virtual_method_number の部分がかなり曖昧ですが、エッセンスとして読んでいただければと思います。
※6のアセンブラコードではよく分からないかと思いますので、まずはオブジェクトのメモリレイアウトを図3に示します。

            図3



vtableと呼ばれるテーブルに呼び出すべき仮想関数の場所(アドレス)が格納されています。
また各objectはvtableの場所(アドレス)を保持する変数(ポインタ)を持っています。
さらに、機械語の特徴のとして関数呼び出し(CALL命令)は、常に同じ場所(アドレス)の関数を呼び出すだけでなく、変数(レジスタ)を通して間接的に呼び出すこともできるようになっています。

以上を踏まえて再度、擬似アセンブラコードを説明しますと、

Aでは、vtableを参照しています。EAXとはレジスタというCPUが持っている変数になりますがそこへvtableのアドレス(vptr)を代入しています。[] というのはアセンブラでのポインタ参照(間接演算子 *)になります。

Bでは、virtual_methodの呼び出すべきアドレスを、EDXに代入します。このvirtual_method_offsetですが配列のインデックスのようなもので、図3では0ということになります。

最後のCのCALL命令が、A,Bを通して取得した呼び出すべき仮想関数の呼び出しを行っていることになります。

このように擬似アセンブラコードを通してみますと、説明は難しいですが、たったの2命令の追加で仮想関数呼び出しを実現しており、C++での仮想関数呼び出しというのはかなり効率的であることが分かります。

もともと、私はアセンブラが大好き(ハードウェアを直接制御できるので)だったのですが、時代に押されてC言語を使うようになりましたが、その理由の一つとしてC言語が高級アセンブラとして設計された(つまりこのように簡単にアセンブラに置き換えられる)から動作がよく理解しやすい面があったからで、その設計思想はC++にも引き継がれていることが分かります。

続いては、公開1周年記念特集記事として『プログラミング言語の制御構造のいろいろ(3)』を書いてみます。
2011-08-04 | コメント:0件
Previous Page | Next Page