この問いの答えがわかりますか
1. 複数のオブジェクト・ファイルを結合してEXEファイルを生成するツールは?
2. プログラム実行時に、データやオブジェクトのために動的に確保されるメモリ領域のことを何という?
答えは
1. リンカー
2. ヒープ領域
です。
本記事では、これらの理解が進むことへの手助けとなるような記事を目指します。
異なる言語を CPU で処理できるようにするために
皆さんはどの言語を使ってプログラムを書いていますか。C, C++, Java, Ruby, PHP, Javascript, Go, Rust, Scala など、数限りないほどの種類のあるプログラミング言語のうちのいくつかを使っているのではないでしょうか。
言語が違うのになぜ、プログラムは動作し、何かを表示したり何かを計算したりすることができるのでしょうか。
それは、異なる言語を、CPU が読める共通の言語へと変換しているからです。
CPU が解釈できるのはネイティブ・コード(マシン語)のみです。
私が普段扱う、Ruby, Javascript, Go など、どの言語においても例外なくマシン語に変換され CPU が理解できる形へと変換されているのです。
そしてこの変換のことをコンパイルと呼び、コンパイル(翻訳)する機能を持ったプログラムのことをコンパイラと呼びます。
マシン語とは
マシン語は 16 進数(2 進数でも表せますが、読みやすさから 16 進数が使われているみたい?)の羅列です。
3E CD 32 00 01 76
こんな感じの数値の羅列です。
コンパイルとマシン語の例
以下のコードをどこの環境でも良いので、コピーして sample.c
とか、なんでも良いのでファイルを作成してください。
#include<stdio.h>
int main() {
printf("Hello, World\n");
return 0;
}
以上のコードをコンパイルしてみましょう。mac であれば、brew で gcc をインストールしてください。
brew install gcc
インストールしたら gcc sample.c -o sample
を実行してみてください。
コンパイルが終わると sample
というファイルが作成されているはずです。このファイルが sample.c をコンパイルしてバイナリファイルに変換された実行ファイルになります。
以下のコマンドで実行でき、
./sample
以下のコマンドでファイルの中身を確認してみましょう
cat sample
このファイルの中身を見るとわかりにくいと思うので、以下のサイトで 16 進数へと変換してみてください。マシン語は変換後の数値で表現されています http://tomeko.net/online_tools/file_to_hex.php?lang=en
リンカー
最初の段階で問題として出したリンカーですが、先ほどのコンパイル時に密接に関わっています。
gcc コマンドによってコードがバイナリへと変換されるわけなのですが、次のような流れで変換が行われています。
コード
→ コンパイルによってオブジェクトファイルへと変換
→ リンカーによって各種関数がコードにリンク
→ 実行ファイル完成
オブジェクトファイルという新しい言葉がでてきましたが、あまり気にせず飛ばしてしまいましょう。
リンカーによって各種関数がコードにリンク
ですが、関数というのはその言語が標準で持っている関数を紐づけて(リンク
)いたりする。
スタックとヒープ
メモリーとディスクとの関係についてで説明したメモリーですが、1つのプログラムが占有する領域では 4 つの領域に分けられます。
- 変数のための領域
- 関数のための領域
- スタックのための領域
- ヒープのための領域
です。
スタックは関数の内部で一時的に使用される(ローカル)変数や、関数を呼び出すときの引数を格納しているメモリ領域 ヒープはプログラムの実行時にデータやオブジェクトを格納するメモリ領域
となっています。
実行ファイルをメモリに読み出すときに、必要な領域が確保されます。
言語によって変わる部分もありますが、int, int64, string, float など型を定義する言語(静的型付け言語)の場合は、アプリケーション実行前に前もって実行用のファイルをコンパイルし、スタック領域の確保のためのコードは自動で生成されるためエンジニアは何も意識する必要がありません。
また c 言語の場合はmalloc, free
関数を使うことで確保と解放を明示的に示しますが、それ以外の場合はヒープ領域のことを特別意識することは少ないでしょう。(知らない言語もあるので言い切れないですが)