- 情報はビットとコンテキスト
以下に示すCプログラム
hello.cを例に考えます:
#include <stdio.h>
int main() {
printf("hello, world\n");
return 0;
}
このソースコードは、実際には0と1のビット列で構成されています。8ビットが1バイトとなり、各バイトは特定の文字を表します。
現代の多くのコンピュータシステムでは、ASCII標準を使用してテキスト文字を表現します。つまり、各文字は一意の単一バイトの整数値で表されます。例えば、
#は35、iは105です。
hello.cプログラムはファイル内にバイト列として保存され、各バイトは特定の文字に対応する整数値を持ちます。
注意: 各行は見えない改行文字
\n(ASCIIコード10)で終了します。
hello.cの表現方法は、以下の基本的な考え方を示しています:
システム内のすべての情報——ディスク上のファイル、メモリ内のプログラム、ユーザーのデータ、ネットワークを介して送受信されるデータなど——はすべてビット列で表現されます。
異なるデータオブジェクトを区別する唯一の方法は、それらを読み取るときのコンテキストです。
読書ノート: コンテキスト: 文章の前後の文脈のように、単独のint型変数だけではその実際の役割を明確に知ることはできません。隣接するビットが与えられることで、具体的な意味が確定します。例えば、
hello.cでは、intのコンテキストから、これは現在の関数の戻り値型がintであることを示していることがわかります。
- プログラムは他のプログラムによって異なる形式に変換される
上記の
hello.cは人間が理解できる高レベル言語で書かれていますが、システム上で実行するためには、他のプログラムによって低レベルの機械語命令に変換され、その後、これらの命令は**実行可能目標プログラム(オブジェクトファイル)**として二進制のディスクファイルに保存されます。
Unixシステムでは、ソースファイルから目標ファイルへの変換はコンパイラドライバーによって行われます:
gcc hello.c -o hello
ここでは、GCCコンパイラドライバーがソースプログラムファイルhello.cを読み取り、それを実行可能な目標ファイルhelloに変換します。具体的な手順は以下の通りです:
(1) プリプロセスステージ: プリプロセッサ(cpp)は、#で始まるコマンドに基づいて元のCプログラムを修正します。ここでは、stdio.hというシステムヘッダファイルを読み込み、それをソースプログラムに挿入し、別のCプログラム(通常はhello.i)を作成します。
(2) コンパイルステージ: コンパイラ(ccl)は、テキストファイルhello.iをテキストファイルhello.sに翻訳します。これはアセンブラ言語プログラムです。
(3) アセンブルステージ: アセンブラ(as)は、hello.sを機械語命令に翻訳し、これらの命令を再配置可能な目標プログラムという形式にパックし、結果を目標ファイルhello.oに保存します。これは二進制ファイルです。
(4) リンクステージ: hello.cはprintf関数を呼び出しています。この関数は、各Cコンパイラが提供する標準Cライブラリの一部であり、printf.oという事前にコンパイルされた目標ファイルに存在します。この段階で、リンカ(ld)はprintf.oをhello.oに結合し、最終的にhelloファイル(実行可能な目標ファイル)を作成します。これにより、システムがメモリにロードして実行することができます。
- コンパイルシステムの動作を理解することは有益である (1) プログラムのパフォーマンスを最適化する
例えば:
switch文は常にif-else文よりも効率的ですか? 関数呼び出しのオーバーヘッドはどの程度ですか?whileループはforループよりも効率的ですか? ポインタ参照は配列インデックスよりも効率的ですか? ループの合計結果をローカル変数に格納する方が、参照渡しの引数に格納するよりも高速になるのはなぜですか?
(2) リンク時のエラーを理解する
最も厄介で位置づけにくいプログラムエラーの多くは、リンカの操作に関連しています。例えば:
リンカが参照を解決できないとはどういう意味ですか? 静的変数とグローバル変数の違いは何ですか? スタティックライブラリとダイナミックライブラリの違いは何ですか? コマンドライン上でライブラリの順序を並べ替えるとどのような影響がありますか?
(3) セキュリティの脆弱性を避ける
バッファオーバーフローは、ほとんどのネットワークやインターネットサーバー上のセキュリティ脆弱性の主な原因です。これは、プログラム開発者が信頼できないソースからのデータの量と形式を制限する必要性を理解していないためです。
安全なプログラミングの第一歩は、データと制御情報がスタック上にどのように保存され、それがどのような結果をもたらすかを理解することです。