第38回
プログラミングの周辺事項(1)~Cで書いたプログラムの仕組みと構造

Cプログラムの構造

Windowsプログラミングに関しては、回を追ってもっと細かく取り上げる予定です。ここでは、コンソールモードのプログラム生成過程について、もう少し説明しましょう。

プログラムはメモリ内で実行される

フォン・ノイマンあるいはENIAC開発チームが発案したとされるプログラム内蔵式コンピュータでは、ディスクなどの記録媒体に記録された実行形式ファイルがメモリに読み込まれ、その段階ではじめてCPUが実行可能なプログラムとなります。このときメモリの中には命令とデータが混在しており、それぞれの位置はメモリ上のアドレスによって示されます。

つまりプログラムは、メモリに読み込まれるまで――記録媒体に保存された状態では『仮の状態』となっている訳です。何が『仮』なのでしょう?それはアドレスです。ディスクなどに記録された状態の実行形式ファイルも当然機械語ですが、命令やデータの位置を示す具体的なアドレスは明示されていません。この段階では、プログラムがメモリのどの位置に読み込まれるのかが明確ではないからです。

アドレスの割り付け

ファイルの状態で、たとえば100番地に存在する命令があったとします。このときの100番地は、プログラムが0番地から始まるという仮定に基づいたあくまで仮の番地です。このプログラムがメモリの1000番地に読み込まれたとすると、先の100番地の命令はメモリ上では1100番地に存在することになります。これでやっと、命令が実行される状態での番地が決まったことになります。

プログラムを生成するということは、メモリに読み込まれたときに正しく動作する機械語のファイルを生成するということです。作ったプログラムがただちに、しかも正しく動作するとは限りません。

実際、コンパイル~リンクの工程を経てできあがったプログラム――実行形式ファイルは、ディスクに記録されている状態では正しく動作しません。OSによってメモリに読み込まれ、その状態でアドレスを正しく割り付けられてはじめてプログラム――OSから見ればプロセス――として動作します。

コンパイルとリンク

コンパイル(compile)の原義は「翻訳」です。これが、ソースコードを機械語に変換する処理を指すことになった訳です。我々が一般に「コンパイル」と言った場合、ソースコードから実行形式ファイルを生成する処理――プログラム生成の全行程をイメージします。

確かに、ソースコードをコンパイルすると2進数の機械語ファイルが生成されます。しかしそうやって生成されたものは中間的なオブジェクトファイルであり、それ単体でCPUあるいはOSが解釈~実行できるものではありません ※7

Cに限らず高級言語を用いて記述されたソースコードをコンパイルして生成されたオブジェクトファイルは、高級言語の処理系に用意されたほかの機能――スタートアップルーチンやライブラリ関数などの実体(これもまたオブジェクトファイル)と結合されなければ、実際に動作するプログラム(実行形式ファイル)にはなりません。

オブジェクトファイルを結合するのがリンカ(linker)です。コンパイラによってオブジェクトファイルを生成し、リンカでそれらを結合することで実行形式ファイルが生成されます。

「コンパイル」という言葉には、ソースコードから実行形式ファイルを生成するところまでを示す「広義のコンパイル」と、ソースコードからオブジェクトファイルを生成するという意味の「狭義のコンパイル」の2つの意味があり、会話の中で混在して使われることもしばしばあります。その場合、両者の違いは文脈から読み取るしかありません

mainが最初ではない?!

Cの場合、スタートアップルーチン(スタートアップオブジェクト)がメモリに読み込まれたプログラムの初期化を行い、ソースコードで示されているmain関数を呼び出して実行します。

Cで記述されたプログラムはmain関数から始まると言われていますが、実際にはスタートアップルーチンが最初に実行されます。そして、スタートアップルーチンでプログラムの初期化を行い、最終的にmain関数を呼び出します。ここから初めて、プログラムの『個性』と呼ばれる部分が実行されることになります。

つまり、一般的なアプリケーションで我々が記述しているのは、スタートアップルーチンから呼び出されるmain関数より後の処理だけ――ということです。

我々は、常に先人の作ったシステムの上に乗っかってモノを作っています。ひとくちに「プログラムを作る」とは言っても、実は先人の作った一定の枠を前提として、単にプログラムの個性と言える「独自の処理部分」だけを作っているに過ぎません。OSや処理系、さかのぼれば言語の仕様でさえ、すべて他者の成果を利用しているだけなのです。

図4:最初にスタートアップルーチンが実行され、そこからmain関数が呼び出される
図4:最初にスタートアップルーチンが実行され、そこからmain関数が呼び出される

あとがき

hiropの『ちょっと気になる(専門?)用語』~《先人の功績》

今回は技術的な専門用語ではなく、ごく普通の言葉を取り上げます。確かにごく普通に使われる言葉ではありますが、かと言って日常頻繁に使われる訳でもありません。この「先人の功績」という言葉には、技術的な意味合いが感じ取れます。

制約の中のオリジナリティ

本文で、OSとアプリケーションの関係について触れました。

WindowsなどのGUI-OSでも、グラフィックスを使わないCUIの環境下でも、OSの配下でシェル・プログラムが動作し、さらにその配下でアプリケーションが動作する点は同じです。また、プログラムがOSの提供する機能を利用する点も変わりません。

つまり、現在のアプリケーション開発は、あらかじめ用意された環境の中であらかじめ用意された機能を使い、目的の処理を組み上げていく――という作業なのです。

このように書くと、なんだかプラモデルを作っているように受け取る人もいるかと思いますが、そうではありません。プラモデルは最終形態が決まっていますが、アプリケーションの最終形態は目的により、あるいは制作者の考え方によってさまざまです。

制約の中でオリジナリティを発揮するのが、現在の創作活動です。このことは、プログラミングに限りません。絵画だって写真だって、音楽だって同じです。

他者の功績あってこそ

「篭に乗る人担ぐ人、そのまた草鞋(わらじ)を作る人」という言葉があります。誰もが他者の働きのお陰で生活できている――という意味です。

もし、紙を漉(す)き、植物や土から絵の具を作って絵を描くとしたら……。もし、箱を組み立ててレンズを磨き、感光乳剤を自分で作って写真を撮るとしたら……。楽器を作るどころか、音階を独自に作って作曲や演奏をするとしたら……。

そんなことを想像するまでもなく、我々のあらゆる活動は、先人の多大な努力の結果の上に成り立っています。

ただ、コンピュータは音楽どころか写真よりも遙かに短い期間でものすごい発展を遂げてきたため、「先人の努力」がほかの技術ほどには歴史あるものとは思いにくいところがあります。なにしろ、現在のコンピュータ技術の基礎を作った人の多くは、まだご存命なのですから。ダヴィンチ ※8 やダゲール ※9 のような、「歴史上の人物」というイメージは湧いてきません。

しかし、Cなどの言語や各種OSはもちろんイーサネット、Webなどの通信技術などなど、実にさまざまな技術の上に我々の開発作業は存在しています。それらを発案、開発した人が歴史上の人物かどうかはともかく、先人に感謝しつつお仕事をしたいと思います。

レオナルド・ダヴィンチ。13世紀に、すでに機械式計算機の設計図を描いていました
ルイ・ジャック・マンデ・ダゲール。ダゲレオタイプ――レンズを通過した光を、銀を蒸着した板に当てることで映像を定着させる装置――の発明者。これが元祖カメラであり、写真術の出発点となりました