リンクフリーを近日中にとりやめる予定です

すでにリンクを貼っていただいている方、ご一報頂きたくお願い申し上げます。


ごく少数ですが、リンクをお断りする場合があります



ブログ内 風景光景カテゴリー

続編記事などをご希望の方は こちらへどうぞ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[未掲載分] 車輪の再発明 (7)

元来、好ましい意味で用いられない「車輪の再発明」。



筆者は、そのような行為を肯定的に捉えている。
なぜならば、新しい技術や文化が芽生えるきっかけは、一見無駄と思われることへの挑戦である・・・


お知らせ
活動休止にともない、この記事を事前に予約投稿してあります。
トップ記事の固定を目的としています


この題目は2012年暮れ頃に掲載しようと下書きし、諸事情により掲載却下とした分です。
前話、車輪の再発明 (6)にひきつづき、利用環境を OS はWindows , CPU は Intel 製、いわゆるウィンテル機を前提に綴ります。

アセンブリファイルを出力

まずアセンブリ言語ファイルの出力について述べておきたい。
アセンブリ言語ファイルを出力する目的は、C/C++ 言語で書いたコードがどのように変換されたかを確認するためである。

Visual Studio の利用を前提に話を進める。初期状態ではアセンブリ言語コードや機械語のファイルは出力されないように設定されている。
プロジェクトのプロパティで簡単に変更できる。
すでに車輪の再発明 (5)で触れている通り、プロジェクトのプロパティを開くには Visual Studio の左側ペイン「ソリューションエクスプローラ」から、プロジェクトファイルのアイコンを右クリック。
※ キーボード操作ならば、[Alt]ボタンを押しながら[F7]を押す。



ダイアログボックス左側ペインのツリービュー、「構成プロパティ」の「C/C++」を選択。
[出力ファイル]の中に、[アセンブリの出力]という項目がある。初期状態では「なし」、ゆえに、出力されない状態に設定されている。



[アセンブリの出力]のドロップダウンリスト ( 右隅にある下向き三角 ) をクリックして
「アセンブリコード」「コンピュータ語コード」「ソースコード」を選択。
これで、「もとのC/C++ 言語コード」、「機械語」「ニーモニックコード」が出力されるようになる。

初期状態でソリューションフォルダー内に Release や Debug というフォルダーが作成されるハズ。それらの中に「.cod」という拡張子が付いたファイルが作成される。
拡張子が「.cod」となっているファイルを開いてみると、もとのソースコード、それに続き 16進数、アセンブリ言語のコードが並んでいる。



16進数の部分がいわゆる「機械語」「マシン語」と呼ばれる部分で、CPU にとって直接理解できる状態。
その右側に並ぶ、call や mov や add の部分は「アセンブラコード」や「ニーモニックコード」と呼ばれる。16進数で羅列される数値だけでは人間にとって判り難いので、それを補う役割がある。例えば加算なら ADD , 乗算なら MUL などなど。

古くから PC に触れているヒトにとってはアセンブリ言語しか選択肢がなかった頃もある。
やがて人間にとって判り易い言語で書いて、翻訳したアプリを実行するようになり、アセンブリ言語を主軸に組むことは減った。
今後、プログラミングを覚えてゆこうとしているヒトにアセンブリ言語や機械語から学び始めるのはお勧めできない。
アセンブリ言語で組むとなると、ハードウェアを直接叩くことになる。ちょっとしたミスで致命的な状況、ブルースクリーンや暴走へとつながる。たとえ熟練のヒトであってもちょっとしたミスは避けられない。
さらに、Visual Studio で 64ビット用のアプリ作成する際にはインラインアセンブラが利用できない。

C/C++ 言語や他のプログラミング言語から機械語に翻訳された過程やプログラミングされたコードの流れを把握することは有益である。アセンブリ言語を読めるようになれば、翻訳、変換の過程から、もっと良いコードを探ることやハードウェアへの理解が深まることだろう。慣れてくれば、トラブルシューティングも迅速に行える。

この題目のきっかけとなったブロガーさんにかぎらず、ハードウェアを深く知らずともアプリを作成することは可能だ。
むしろ、高水準な言語、お手軽な言語でコード入力等に要する時間をいかに削減するのが時代の流れである。
アプリを作れるようになると「どうすれば高速に動くのか」「ハードウェアの性能を引き出したい」など興味が沸くも自然な流れである。しかし、ハードウェアに対する理解が浅い状態で「高速に~」を試みるのは巨大な風車に立ち向かうドンキホーテのようなものだ・・・

実行ファイルの切り替え

標準的なモノと少々細工を加えたモノ、さらに深くチューニングしたモノ、それぞれを比べたい。何を比べようとしているかについては、過去記事車輪の再発明 (2)で述べた通り。
複数のモノを比べる際、「対象を手軽に切り替えたい。」と考えるのは自然なコト。
ここで比較対象とは実行ファイルを指す。

実行ファイルを切り替えようとして、簡単に思いつく方法は、
・プロジェクトのコピーを複数用意して、それぞれに別々のコードを書く
・単一の実行ファイル内に「開始時刻の取得 ~ 測りたい処理 ~ 終了時刻の取得」を複数設ける
・「測りたい処理」部分を関数ポインタで動的に変更する
・条件つきコンパイルで複数の実行ファイルを作成し、それぞれを実行して比較。
などだろうか。

まず、最初の「複数のプロジェクトファイル~~」は即却下。理由としては管理の手間が増え、修正漏れなどのミスが生じ易い。

同様に
・単一の実行ファイルで「測りたい処理」を作る度にビルドし実行、計測してゆく
といった力任せな手法も避けたいものだ。修正漏れなどのミスが生じ易いだけでなく、ビルドする度に多くのメモリを費やしたり、メモリの断片化が起こる。安定した状態での比較は難しくなる。
安定した条件で比較することを考えると、PC を再起動し数分間経った後の「PC がヒマな状態」で計測するのがベスト。ディスクのアクセスLEDやネットアクセスを示すLEDの点等が著しい間はヒマな状態とはいえない。起動した後何も作業していない状況ならば未使用のメモリ空間も多く残っているハズ・・・

今回は2番目の、単一の実行ファイル内に「開始時刻の取得 ~ 測りたい処理 ~ 終了時刻の取得」を複数設ける案が無難な選択肢となるだろう。
CPU の キャッシュメモリ容量に左右されてしまう内容であれば要注意だが、今回はキャッシュに収まりきれない容量なので配慮は不要。
※ キャッシュ容量に左右されないようにするには、キャッシュの内容をクリアするコードを追加すれば対処できる

3番目の関数ポインタうんぬんに関して言えば、検索サイト等からこの記事にたどりつくヒトを想定するならば敷居が高い話となる。よって今回は却下。

最後の「複数の実行ファイル」や「条件つきコンパイル~」は不慣れなヒトのために載せておきたい。
まず、複数の実行ファイルを作成するにはバッチビルドを行うと楽。
同じプロジェクトで構成を少し変えて実行するには便利である。もちろん、複数のプロジェクトを一括ビルドする際にもバッチビルドが活きてくる。
プロジェクトの設定変更については、過去記事車輪の再発明 (4)で取り上げた。

バッチビルドを行うにはいくつか方法がある。今までの流れに従い Visual Studio の利用を前提に話を進める。
メニュバーの[ビルド]をクリックし、表示されたメニューの中から[バッチビルド]を選択。もしくは、キーボードの[Alt]ボタンを押しながら[B]ボタンを押し、[T]ボタンを押す。



ここで表示されたダイアログボックスのリストビューで構成を確認できる。



リストビューのカラムヘッダは横方向に「プロジェクト」「構成」「プラット・・・」と並んでいる。
「構成」の行、縦方向に「Debug」「Relase」と並んでいる。
過去記事車輪の再発明 (4)で触れたように、「Debug」と「Relase」の違いは「リハーサル用」と「本番用」のように考えると判り易いだろう。

「プラット・・・」の行は過去記事車輪の再発明 (4)で64ビット向けのアプリを作れるように設定変更したので、Win32 , x64 と交互に並んでいるハズ。

ここに、比較用にパターン2を追加してみる。
新しい構成を追加するには、ひとつ前の図、メニュバーの[ビルド]をクリックしてメニューがポップアップされた様子をご覧いただくと判り易い。メニューの項目「バッチビルド」のひとつ下に表示されている「構成マネージャ」を選択。
もしくは、キーボードの[Alt]ボタンを押しながら[B]ボタンを押し、[O]ボタンを押す。



ほかにも、Visual Studio のメニューバー下、ソリューション構成のドロップダウンリストをクリックして「構成マネージャ」を選択する方法もある。

構成マネージャのダイアログボックス左側、



「アクティブソリューション構成」のドロップダウンリストから「<新規作成>」をクリック。



「新しいソリューション構成」のダイアログ、[ 名前 (N) ]のエディットボックスに追加したい件の名称を入力する。
ここでは、Release 版、第2パターンということで「Release_p2」と設定した。さらに第3番目のパターンを追加するならば「Release_p3」などなど・・・



新しい構成は Release 版をベースに作成したい。[ 設定のコピー元 ]のドロップダウンリストから「Release」を選択する。
このドロップダウンリストが「空」のままであったり、「Debug」が選択されているようなコトが無いように確認・・・

順調に進めば、バッチビルドのダイアログ、リストビューに第2番目のパターンの構成が追加される。



この手順で、第3、第4、第5と比較したい構成を追加してゆく・・・

条件つきコンパイル

C/C++ 言語にかぎらず、「条件つきコンパイル」という仕組みがある。
おおまかな部分はほぼ共通、一部は個別の状況に合わせて差し替えたいといった場合に好都合。
条件によって翻訳の原文を切り替えることができるので、一つのソースコードを基に複数の異なる条件で共有したい場合などに便利。

C/C++ 言語においてはプリプロセッサディレクティブ、#define ディレクティブ#if ディレクティブ ~~ #else ディレクティブ ~~ #endif ディレクティブを組み合わせると手軽に切り替えることが可能。

#define ディレクティブには複数の使い方があり、主な使い方を2通り取り上げる。「#define 定数名」と「#define 定数名 数値や文字列」では意味合いが異なる。

「#define ディレクティブ」の前者の例としては
#ifdef _DEBUG
// Debug 版で用いるコード
#endif //_DEBUG

といったところが判りやすい。「_DEBUG」 というマクロが定義されていれば「#ifdef」 と「#endif」で挟まれた部分が有効になる。上の例は「もし ○○ ならば△△」の意味合いで「#ifdef] と「#endif」で挟んでいる形。
「もし ○○ ならば△△それ以外は■■」のように使いたいならば

#ifdef _DEBUG
// Debug 版で用いるコード
#else
// Debug 版以外で用いるコード
#endif //_DEBUG

のように書く。違いは「#else」が挿入されている点。これにより、「_DEBUG」が定義されていないならば 「#else」から「#endif」の間のコードが有効になる。
#ifdef 定数名#if defined(定数名) と書いても同じ意味となる。
例えば、64ビット版のアプリと32ビット版でアプリ毎に別々のコードを適用したいならば
#if defined(WIN64) || defined(_WIN64)
// 64ビット版向けのコード
#else
// 32ビット版向けのコード
#endif // defined(WIN64) || defined(_WIN64)

といった感じで使うのも良いだろう・・・

※ Intel 製 CPU 、OS は Windows という状況において、64ビット版の OS では 64ビット版向けのコードも32ビット版向けのコードも実行可能。32ビット版の OS では32ビット版向けのコードと16ビット版向けのコードが実行可能であるが、32ビット版の OS では 64ビット版向けのコードは実行できない。

「#define ディレクティブ」の後者の例としては、定数名に数値が結びつくパターン。
参考例としては、ヘッダーファイル「targetver.h」もしくは「stdafx.h」の中に記されている「#define WINVER 0x0600」が判り易いのではないだろうか。「最低限必要なプラットフォームが Windows Vista であることを指定~~」と注釈が付いていることから、おおよその意味を察することができるハズ。
実は、Windows Vista より前、Windows Vista 以降では挙動が異なる例がいくつかある。Windows Vista より前ではサポートされていない機能もある。
アプリの実行条件・対象を明確に指定することで、サポートされていない機能を使おうとして生じるエラーなど、実行時のトラブルを未然に防ぐのに役立つ。

最低限必要なプラットフォームを Windows Vista と設定して #if ディレクティブを使うならば
#if defined(WINVER) && (WINVER >= 0x0600)
// Windows Vista 以降向けのコード
#endif // defined(WINVER) && (WINVER >= 0x0600)

のようになるだろう。さらに条件の一致と不一致で分けるならば #else ディレクティブ を間に挿し、

#if defined(WINVER) && (WINVER >= 0x0600)
// Windows Vista 以降向けのコード
#else
// Windows Vista より前向けのコード
#endif // defined(WINVER) && ( WINVER >= 0x0600 )

と使うこともできる。
ところで、WINVER マクロで有効な値、すなわちWindows のバージョンを示す数値として
Windows 2000 は 0x0500 、XP ならば 0x0501 、Windows7 は 0x0601 、Windows8 は 0x0602 などが定義されている。
Windows のバージョンごとに細分するならば
#if defined(WINVER) && (WINVER >= 0x0601)
// Windows 7 以降 向けのコード
#elif defined(WINVER) && (WINVER >= 0x0600)
// Windows Vista 以降 向けのコード
#elif defined(WINVER) && (WINVER >= 0x0500)
// Windows 2000 や XP 向けのコード
#else
// どの条件にも 一致しない Windows 向けのコード
#endif // defined(WINVER) && (WINVER >= 0x0601)

といった使い方もできる。実際は新規に何かを作成する場合には将来に向けたモノを作るべきであり、古い OS やサポート期間が終了した OS を特定の対象にすることは稀有だろう・・・

#define ディレクティブ#if ディレクティブ ~~ #else ディレクティブ ~~ #endif ディレクティブに慣れてしまうほど使いたくなるものだ。しかし、用い過ぎると読み易さが損なわれる。
ちなみに筆者は「#endif」 の直後にコメントを付けるよう心がけている。「#if 」と「#endif」は対にして、ワンセットにすることになっている。どちらかが欠けてしまうとエラーで翻訳できない。重複して使うことは可能だが、時には入れ子が深くなり「この#endifはどの#ifに対するモノ???」 といった状況にもなりかねない・・・

ここで誤解が生じないように補足。
ここまでの話は「#define ディレクティブや #if ディレクティブなどの使い方」がキモであり、プラットフォームをリアルタイム判定しているのではない。
アプリをビルドする段階では「どのバージョンのWindows 上でアプリが実行されるのか」を知ることができない。
利用環境は十人十色、つまり、アプリが実行されるまでどのバージョンのプラットフォームなのかは不明である。
その辺について知りたいヒトもいることだろう。
アプリの中にGetVersionEx 関数 や VerifyVersionInfo 関数などを設けることで現在のプラットフォーム・Windows のバージョンが数値として得られる。
その数値をもとに、最低限必要なバージョン番号およびそれより大きいバージョン番号であるかどうかをチェック。もし、バージョンが低いならばメッセージボックスを表示してアプリを終了する。といった流れにすれば使う側から観て親切。
バージョンチェックを追加する場所はウィンドウフォームを表示する前、できれば WinMain 関数の冒頭で行うのが良いだろう。

※ GetVersionEx 関数 や VerifyVersionInfo 関数 を検索するためのリンクを貼っておきます。詳しく知りたいヒトはMSDN ライブラリ、できれば日本語版だけでなく原文にも目を通せば情報の見落としが減ることでしょう。
GetVersionEx関数を検索するためのリンク
VerifyVersionInfo関数を検索するためのリンク

アプリ毎に定数を設定

しばしば机上の論と実情は異なる。
教科書的に#define ディレクティブでの切り分けとなると、実際のところパターン毎に設定を書き換えるので微妙に不便。
ちょっとしたミスが含まれていた場合は手直ししたくなるものだ。その際、パターンの数だけ「#define ○○」の部分を書き直し全ての構成をビルドし直すことになり面倒。

複数ビルドの手間を考えると、プロジェクトの構成ごとにプリプロセッサ設定を変更するのが楽である。
例として、「__MYAPP_LEVEL」という定義名を用意してパターン毎に違う数値を割り当ててみる。
※ 定義名はこの例の通りでなくとも構わない。ただし31文字以内で収まるように注意すべきである。

プリプロセッサの定義を変更するには、プロジェクトのプロパティを開いて個別設定を行う。
プロジェクト毎の設定を変更する件は車輪の再発明 (4)で述べた。また、この記事の冒頭でアセンブリファイルを出力する件として触れている。

プロジェクトのプロパティを開いて個別設定を行うには、操作画面メニューの「プロジェクト」 -> 「プロパティ」とクリックするよりも、キーボードの[Alt]ボタンを押しながら[F7]を押すと楽。

ダイアログボックス左側ペインのツリービュー、「構成プロパティ」の「C/C++」を選択。
さらに ツリービューの[プリプロセッサ]を選択。



ダイアログ右側に「プリプロセッサの定義」という項目がある。ここに追加したい定数等を入力する。

車輪の再発明 (4)の例では64ビット版のアプリを作りたいので
「;WIN64;_WIN64; ~~ ;UNICODE」のを付け加えた。その後ろに
;__MYAPP_LEVEL=2
を付け加える。区切りのセミコロン「;」を忘れない、空白を入れないように注意。



「これは第2番目のパターンですよ」という意味だ。 同様に3番目のパターンのコードには ;__MYAPP_LEVEL=3 4番目のパターンのコードには ;__MYAPP_LEVEL=4 といった具合に変更を加えてゆく・・・

先に述べた#if ディレクティブ ~~ #else ディレクティブ ~~ #endif ディレクティブを使うとして、
ソースコードは以下のような感じ。
#if __MYAPP_LEVEL == 2
// 第2番目のパターンのコード
#elif __MYAPP_LEVEL == 3
// 第3番目のパターンのコード
#elif __MYAPP_LEVEL == 4
// 第4番目のパターンのコード
#else
// どの条件にも 一致しないコード
#endif //__MYAPP_LEVEL == 2

※ __MYAPP_LEVEL が定義されているか不確実な時は #if defined(__MYAPP_LEVEL) && (__MYAPP_LEVEL == 2) と書いたほうが丁寧。今回は確実に定義されているとして、省いています。

複数の実行ファイルを一箇所に作成

ここまで順調に進めば複数のアプリを作成できるハズ。

実行ファイルの名称が全て同じであり、別々のフォルダーに格納される。
単一の実行ファイルを作成している場合、不便に感じることは少ないことだろう。一方、複数のアプリを作成した場合、アプリを起動する度に別のフォルダーを開いたり、戻ったりの手間が増える。面倒という声が聞こえてきそうだ。
ならば、同じフォルダーに名称の異なる実行ファイルが作成されるように設定変更すれば手間が減る。

実行ファイルの出力先を変更する件については車輪の再発明 (4)の終盤で触れた。
プリプロセッサの定義を変更した際と同様、プロジェクトのプロパティを開いてプロジェクト毎に設定を変更する。



ダイアログボックス左側ペインのツリービュー、[リンカ]の[全般]を選択。
そして、右側にある[出力ファイル]という項目に注目。



初期状態で出力先は
$(OutDir)\$(ProjectName).exe
などと設定されているハズ。ここを変更する。

例えば、第2番目のパターンのコードを作成したいなら
$(SolutionDir)$(ProjectName)_p2.exe
と変更する。同様に3番目のパターンのコードならば
$(SolutionDir)$(ProjectName)_p3.exe
4番目のパターンのコードならば・・・と設定を変更してゆく。

バッチビルドのダイアログボックスで、ビルドしたい全てのパターンにチェックを入れ[ ビルド ]ボタンをクリックする。
一括ビルドが順調に完了すれば以下の画像のようになる。



同一フォルダー内に名称の異なる実行ファイルが作成されるハズ。
これで複数パターンの実行ファイルを比べるのが楽になりそうだ・・・

長くなりましたので続きはまた後日・・・
スポンサーサイト

本日も最後までご覧いただきありがとうございます。

「つまらなかった」「判り辛った」という方もご遠慮なくコメント欄へどうぞ

テーマ : プログラミング
ジャンル : コンピュータ

検索サイトからお越しの方へ
検索サイトからお越しの方は、ブラウザのアドレス欄vitalaboloveおよび、fc2.comが含まれているかご確認ください。
含まれていない場合、偽サイトを閲覧なされている可能性があります。

偽サイトは、当ブログの文字部分や画像部分が有害サイトへのバナーと置き換わっているようです。
プロフィール

Author:Vitalabolove
ご訪問ありがとうございます。
店長を任されておりますVitalaboloveです。

コメントはお気軽に。
今のところリンクフリーですが、あと数日でとりやめます。

画像データ、文言の引用は事前連絡くださるようお願い申し上げます。事前連絡の際は、左下、メールフォームを経由をご利用ください。

最新記事
カレンダー
05 | 2014/06 | 07
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 - - - - -
カテゴリ
ランキング
いつも応援いただきありがとうございました。ただいま休養中につきランキングへ参加していません・・・

フリーエリア
内緒話などはおきてがみをご利用ください。
月別アーカイブ
メールフォーム
掲載された記事について、ご不明な点はここからお問い合わせください

名前:
メール:
件名:
本文:

最新コメント
最新トラックバック
スパムと思われるトラックバックは削除しました
QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。