この記事は、絵でわかる プログラムとは何か(6)~シンボル~ の最初に読んでいただくべき記事です。
この記事のポイント
- 参照先をアドレス(数値)で表す代わりに、文字列による呼び名を付けたものをシンボルと言う。
- シンボルと仮想メモリアドレスとの対応表をシンボルテーブルと言う。
- シンボルの宣言では、中身のデータ型と、その初期値を指定する。(指定しない場合もある)
- シンボルの中身のデータ型が、出力先で対応していない場合には、意図しない結果になる。
- シンボルの中身を、出力先が対応するデータ型にするために、型変換を行う場合がある。
- 型変換には、プログラミングで明示的に型変換をするキャストと、出力先での暗黙的な型変換がある。
- 言語処理系は、データ型に合わない処理を構文エラーにするなど、デバッガの機能を兼ね備えている。
- シンボルよりもさらに広い概念として、識別≒特定 に使われる文字列を識別子と言う。
シンボル
番地指定の参照はわかりにくい
プログラムとは何か(5)~データ編~ のアドレスのところでも述べたように、プログラムで扱うデータとしては、使い回しがしやすいように、即値よりも参照の方がよく使われます。
しかし、参照先のアドレスは、メモリの先頭からの連番、つまり数値です。たとえ仮想アドレス空間で、固定のアドレスにできたとしても、やはり数値のままだと、その中身を想像して扱うのは難しくなります。
たとえば、
[10番地の中身] + [100番地の中身] を、[1000番地の中身] に入れる
と書かれたプログラムが正しいかどうかは、そこだけ見てもわかりません。
仮想アドレスに呼び名を付ける
しかし、もしこれが、
[男性人数] + [女性人数] を、[全体人数] に入れる
だったら、正しいとすぐわかりますし、
[男性平均体重] + [女性平均身長] を、[年間平均気温] に入れる
だったら、全くナンセンスだとすぐわかります。
そこで仮想メモリアドレスに対して、文字列による「呼び名」を付けられる機能が、アセンブラや高級言語の処理系に入れられました。
各参照先アドレスに、入る中身に合わせた自然言語的な呼び名が付けられれば、その中身を想像してプログラミングができるようになります。
この文字列による呼び名をシンボルと言います。
また、言語処理系がこの呼び名処理に使う、仮想メモリアドレスとシンボルとの対応表をシンボルテーブルと言います。
宣言
特定の仮想メモリアドレスにシンボルを対応させるためには、プログラムのソースコード中で、シンボルとしての「宣言」を行います。
宣言では、シンボルと、そのアドレスに入るべき中身のデータ型、場合によっては、さらにその中身自体(初期値)を明記し、言語処理系へ伝えます。
一般的な宣言の形式:
データ型 シンボル = 中身
中身は宣言時に省略できる場合もあります(代入の “=” も合わせて省略します)。その場合、数値型なら0、文字列型なら””(空)といった言語仕様で決められたデフォルト値が、暗黙の内に中身の初期値として入れられます。
また、ソースコードのプログラミング言語とデータ型によっては、データ型ごとに決められた記号( []、()、{}、* など )と合わせて宣言する必要があります。
宣言の例:
シンボルから、データ型ごとに決められた記号を除いた名前部分を、特にシンボル名と言います。シンボル名は、この後で説明する識別子にする必要があります。
シンボルテーブルへの記録
言語処理系は、ソースコードをマシンコード(オブジェクトファイルまたは実行ファイル)へ変換する際に、ソースコード中に宣言されたシンボルをシンボルテーブルへ登録します。
そして、各シンボルが対応する仮想メモリアドレスも、リンカやローダによってプログラム実行時までに、シンボルテーブルに追記されます。
プログラム実行中におけるシンボルの中身
シンボルの中身(シンボルが対応するメモリアドレスに記録されたバイナリデータ)は、プログラム実行によって書き換えられる場合もあります。
そしてプログラム中に、データ型を指定してシンボルの中身を出力する指示が書かれていれば、シンボルの中身のデータは、指定されたデータ型として、出力先に渡されます。
C言語で「symbolの中身を文字列型として画面表示する」例
printf(%s, symbol);
%sによって、文字列(string)型を指定しています。
なお、言語処理系によっては、プログラムでデータ型を指定していなくても、シンボルテーブルに記録されたデータ型として、シンボルの中身のデータを出力先に渡すものもあります。
エラー
出力先でのデータの扱い
出力先に渡されたデータは、出力先がそのデータ型に対応していれば、適切に出力されます。
出力先の対応には、出力先で暗黙的な型変換が行われることにより、適切に出力される場合も含まれます。
出力先がそのデータ型に対応していない場合は、エラーになったり、意図しない出力結果になったりします。
例えば、テキストデータ表示用の画面に、文字型や数値型のデータは出力できますが、バイナリ型のデータは、多くの場合、正しく出力できません。
逆に、プログラミングで、出力前に明示的に型変換しておく処理のことをキャスト(cast)と言います。
cast は、「型に入れる」という意味で、「石膏や金属の鋳造」、「光による造影」、「目標への投げ入れ」、「配役」は皆 “cast” です。
言語処理系によるデータ型チェック
データを処理するプログラムにミスがあった場合も、出力結果のバイナリデータを元のデータ型に復元できなかったり、復元できたとしてもおかしな結果になりかねません。
プログラムミスの例
例えば、[文字型]と[文字型]を掛け算するようなミスをしたプログラムをそのままマシンコード化すると、プロセッサーはその数値化された文字コード同士を掛け算処理することになります。そして得られた結果の数値を文字コードとみなして、文字コード表を検索し、おそらく意図しない結果を出力するでしょう。
【プログラムミスの例】
正) 2 × 3 → 6 (数値で掛け算)
誤) 2 × 3 → ৶ (文字で掛け算)
2[50] × 3[51] → ৶[2550]
[]内はバイナリデータ(文字コード)。わかりやすくするため、ここでは10進数で表しました。
この例が示すのは、2 × 3 の結果として、本来は数値で掛け算して、6[54] という結果を期待していましたが、文字コードで掛け算して、そうはならなかったということです。
正しくは、
- 掛け算の対象( 2[50] と 3[51] )が、数字どうか( [48~57] であるかどうか)をチェックする。 → 2[50] と 3[51] は数字
- 数字であれば、文字ではなく数値として扱うために、数字を数値化する。(各文字コードから [48] を引く)
- 掛け算をする。( [2] × [3] → [6] )
- 掛け算した結果 [6] を文字化する。( [48] を足し戻す)
- 6[54] を結果として表示する。
とするべきでした。
情報処理では、数字は「字」、つまり文字であり、数値は「値」、つまり直接計算できるもの、として使い分けます。
構文エラー(syntax error)
このようなミスを防ぐために、元のデータ型に合わない処理、あるいは元のデータ型定義から逸脱し元のデータ型へ復元できなくなるような処理は、通常、ソースコードからマシンコード(オブジェクトファイルまたは実行ファイル)への翻訳時(主にコンパイル時)に、構文が正しくないということで、言語処理系が構文エラーを出力します。
エラー出力とデバッグ
一般にエラー出力には、エラーコードやエラーメッセージなどが含まれ、そこにエラーの詳細情報(いつ、どこで、何が、どう…)が出力されます。(内容は、言語処理系やその設定によって異なります)
構文エラーに限らず、プログラミングのミスのことをバグ(bug)と言います。プログラムからバグを取り除く作業をデバッグ(debug)と言い、上記の言語処理系によるエラー判定機能のように、デバッグを目的とするプログラムや作業者のことをデバッガ(debugger)と言います。
なお、シンボルがバイナリ型で宣言されている場合、そのバイナリデータ化前のデータ型(音声か?、画像か?、はたまた何か?)は言語処理系にはわかりません。そのため、言語処理系としてはバイナリ型をそれ以上チェック(デバッグ)することはできなくなります。
逆に言えば、異なるデータ型への変換処理や、大量データの高速処理が必要な場合には、ノーチェックで処理を進められるバイナリ型が好まれます。
識別子(ID)
個々のシンボル名やデータ型のように、何を指すかが定義されている名前のことを識別子(identifier)と言います。”identifier” は略して “ID” と言った方が、すでにおなじみかもしれません。
具体的には、特定のメモリ領域(先頭アドレスおよびバイト数)を指す名前が識別子になります。これには、ユーザープログラムで使われるシンボルの参照先アドレスだけでなく、言語処理系プログラム内のデータ型等の定義も含まれます。
社会一般で使われるIDは番号になっていることもありますが、プログラミングでは識別子が文字列になっている方が、識別子が指すものをイメージしやすいため、簡潔かつ他と重複しない文字列が識別子としてよく使われます。
異なる複数の識別子が、同一のメモリ領域を指すことも可能ですが、同一の識別子が、異なる複数のメモリ領域を指すことはできません。
識別子とシンボルの違い
シンボルは、参照先アドレスに付けられる「別名」であるので、当然シンボルも識別子に含まれます。しかし、識別子が意味する範囲は、シンボルよりももっと広く、識別≒特定 に使われるものは全て識別子になります。逆に特定に使えないものは識別子とは言えません。
この記事のまとめ
参照先をアドレス(数値)で表す代わりに、文字列による呼び名を付けたものをシンボルと言い、
シンボルは、言語処理系によって、シンボルテーブルに、仮想メモリアドレスとの対応付けが記録されます。
シンボルの宣言では、中身のデータ型と、その初期値を指定します。ただし、言語によっては、データ型を指定しなかったり、データ型によっては、初期値を指定しない場合もあります。
シンボルの中身のデータ型が、出力先で対応していない場合には、意図しない結果になります。そこで言語処理系は、あらかじめデータ型に合わない処理を構文エラーにするなど、デバッガとしての機能も兼ね備えています。
シンボルの中身を、出力先が対応するデータ型にするために、型変換を行う場合もあります。
型変換には、プログラミングで明示的に型変換をするキャストと、出力先での暗黙的な型変換があります。
シンボルよりもさらに広い概念として、識別≒特定 に使われる文字列を識別子と言います。
次は、リテラル に進みましょう。
この記事は、絵でわかる プログラムとは何か(6)~シンボル~ の一記事です。
コンピューターのしくみ全体を理解したい場合は、以下の2コースがお勧めです。
日本全国 オンラインレッスン にも対応しています。
知りたいことだけ単発で聞きたい場合は、 オンラインサポート をご利用ください。