シンボル

シンボル

  • このエントリーをはてなブックマークに追加
int 宣言の例

この記事は、絵でわかる プログラムとは何か(6)~シンボル~ の最初に読んでいただくべき記事です。

この記事のポイント

  • 参照先をアドレス(数値)で表す代わりに、文字列による呼び名を付けたものをシンボルと言う。
  • シンボルと仮想メモリアドレスとの対応表をシンボルテーブルと言う。
  • シンボル宣言では、中身のデータ型と、その初期値を指定する。(指定しない場合もある)
  • シンボルの中身のデータ型が、出力先で対応していない場合には、意図しない結果になる。
  • シンボルの中身を、出力先が対応するデータ型にするために、型変換を行う場合がある。
  • 型変換には、プログラミングで明示的に型変換をするキャストと、出力先での暗黙的な型変換がある。
  • 言語処理系は、データ型に合わない処理を構文エラーにするなど、デバッガの機能を兼ね備えている。
  • シンボルよりもさらに広い概念として、識別≒特定 に使われる文字列を識別子と言う。

シンボル

番地指定の参照はわかりにくい

プログラムとは何か(5)~データ編~ アドレスのところでも述べたように、プログラムで扱うデータとしては、使い回しがしやすいように、即値よりも参照の方がよく使われます。

しかし、参照先のアドレスは、メモリの先頭からの連番、つまり数値です。たとえ仮想アドレス空間で、固定のアドレスにできたとしても、やはり数値のままだと、その中身を想像して扱うのは難しくなります。

たとえば、

[10番地の中身] + [100番地の中身] を、[1000番地の中身] に入れる

と書かれたプログラムが正しいかどうかは、そこだけ見てもわかりません。

仮想アドレスに呼び名を付ける

しかし、もしこれが、

[男性人数] + [女性人数] を、[全体人数] に入れる

だったら、正しいとすぐわかりますし、

[男性平均体重] + [女性平均身長] を、[年間平均気温] に入れる

だったら、全くナンセンスだとすぐわかります。

そこで仮想メモリアドレスに対して、文字列による「呼び名」を付けられる機能が、アセンブラや高級言語の処理系に入れられました。

各参照先アドレスに、入る中身に合わせた自然言語的な呼び名が付けられれば、その中身を想像してプログラミングができるようになります。

この文字列による呼び名をシンボルと言います。

また、言語処理系がこの呼び名処理に使う、仮想メモリアドレスとシンボルとの対応表シンボルテーブルと言います。

シンボルテーブルのイメージ
シンボルテーブルのイメージ
(実際のシンボルテーブルには、上記以外の項目も登録されています。)

宣言

特定の仮想メモリアドレスにシンボルを対応させるためには、プログラムのソースコード中で、シンボルとしての「宣言」を行います。

宣言では、シンボルと、そのアドレスに入るべき中身のデータ型、場合によっては、さらにその中身自体(初期値)を明記し、言語処理系へ伝えます。

一般的な宣言の形式:

データ型 シンボル = 中身

中身は宣言時に省略できる場合もあります(代入の “=” も合わせて省略します)。その場合、数値型なら0、文字列型なら””(空)といった言語仕様で決められたデフォルト値が、暗黙の内に中身の初期値として入れられます。

また、ソースコードのプログラミング言語とデータ型によっては、データ型ごとに決められた記号( []、()、{}、* など )と合わせて宣言する必要があります。

宣言の例:

int 宣言の例
function 宣言の例
var 宣言の例

シンボルから、データ型ごとに決められた記号を除いた名前部分を、特にシンボル名と言います。シンボル名は、この後で説明する識別子にする必要があります。

シンボルテーブルへの記録

言語処理系は、ソースコードをマシンコード(オブジェクトファイルまたは実行ファイル)へ変換する際に、ソースコード中に宣言されたシンボルシンボルテーブルへ登録します。

アドレス未定のシンボルテーブル
アドレス未定のシンボルテーブルのイメージ

そして、各シンボルが対応する仮想メモリアドレスも、リンカローダによってプログラム実行時までに、シンボルテーブルに追記されます。

プログラム実行中におけるシンボルの中身

シンボルの中身(シンボルが対応するメモリアドレスに記録されたバイナリデータ)は、プログラム実行によって書き換えられる場合もあります。

そしてプログラム中に、データ型を指定してシンボルの中身を出力する指示が書かれていれば、シンボルの中身のデータは、指定されたデータ型として、出力先に渡されます。

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] という結果を期待していましたが、文字コードで掛け算して、そうはならなかったということです。

正しくは、

  1. 掛け算の対象( 2[50] と 3[51] )が、数字どうか( [48~57] であるかどうか)をチェックする。 → 2[50] と 3[51] は数字
  2. 数字であれば、文字ではなく数値として扱うために、数字を数値化する。(各文字コードから [48] を引く)
  3. 掛け算をする。( [2] × [3] → [6] )
  4. 掛け算した結果 [6] を文字化する。( [48] を足し戻す)
  5. 6[54] を結果として表示する。

とするべきでした。

情報処理では、数は「字」、つまり文字であり、数は「値」、つまり直接計算できるもの、として使い分けます。

構文エラー(syntax error)

このようなミスを防ぐために、元のデータ型に合わない処理、あるいは元のデータ型定義から逸脱し元のデータ型へ復元できなくなるような処理は、通常、ソースコードからマシンコード(オブジェクトファイルまたは実行ファイル)への翻訳時(主にコンパイル時)に、構文が正しくないということで、言語処理系構文エラーを出力します。

エラー出力とデバッグ

一般にエラー出力には、エラーコードやエラーメッセージなどが含まれ、そこにエラーの詳細情報(いつ、どこで、何が、どう…)が出力されます。(内容は、言語処理系やその設定によって異なります)

構文エラーに限らず、プログラミングのミスのことをバグ(bug)と言います。プログラムからバグを取り除く作業をデバッグ(debug)と言い、上記の言語処理系によるエラー判定機能のように、デバッグを目的とするプログラムや作業者のことをデバッガ(debugger)と言います。

なお、シンボルバイナリ型で宣言されている場合、そのバイナリデータ化前のデータ型(音声か?、画像か?、はたまた何か?)は言語処理系にはわかりません。そのため、言語処理系としてはバイナリ型をそれ以上チェック(デバッグ)することはできなくなります。

逆に言えば、異なるデータ型への変換処理や、大量データの高速処理が必要な場合には、ノーチェックで処理を進められるバイナリ型が好まれます。

識別子(ID)

個々のシンボル名やデータ型のように、何を指すかが定義されている名前のことを識別子(identifier)と言います。”identifier” は略して “ID” と言った方が、すでにおなじみかもしれません。

具体的には、特定のメモリ領域(先頭アドレスおよびバイト数)を指す名前識別子になります。これには、ユーザープログラムで使われるシンボルの参照先アドレスだけでなく、言語処理系プログラム内のデータ型等の定義も含まれます。

社会一般で使われるIDは番号になっていることもありますが、プログラミングでは識別子が文字列になっている方が、識別子が指すものをイメージしやすいため、簡潔かつ他と重複しない文字列識別子としてよく使われます。

異なる複数の識別子が、同一のメモリ領域を指すことも可能ですが、同一の識別子が、異なる複数のメモリ領域を指すことはできません。

識別子

識別子とシンボルの違い

シンボルは、参照先アドレスに付けられる「別名」であるので、当然シンボル識別子に含まれます。しかし、識別子が意味する範囲は、シンボルよりももっと広く、識別≒特定 に使われるものは全て識別子になります。逆に特定に使えないものは識別子とは言えません。

この記事のまとめ

参照先をアドレス(数値)で表す代わりに、文字列による呼び名を付けたものをシンボルと言い、
シンボルは、言語処理系によって、シンボルテーブルに、仮想メモリアドレスとの対応付けが記録されます。

シンボル宣言では、中身のデータ型と、その初期値を指定します。ただし、言語によっては、データ型を指定しなかったり、データ型によっては、初期値を指定しない場合もあります。

シンボルの中身のデータ型が、出力先で対応していない場合には、意図しない結果になります。そこで言語処理系は、あらかじめデータ型に合わない処理を構文エラーにするなど、デバッガとしての機能も兼ね備えています。

シンボルの中身を、出力先が対応するデータ型にするために、型変換を行う場合もあります。
型変換には、プログラミングで明示的に型変換をするキャストと、出力先での暗黙的な型変換があります。

シンボルよりもさらに広い概念として、識別≒特定 に使われる文字列を識別子と言います。

次は、リテラル に進みましょう。

この記事は、絵でわかる プログラムとは何か(6)~シンボル~ の一記事です。

コンピューターのしくみ全体を理解したい場合は、以下の2コースがお勧めです。

日本全国 オンラインレッスン にも対応しています。

知りたいことだけ単発で聞きたい場合は、 オンラインサポート をご利用ください。

記事を検索

コメントを残す

*

CAPTCHA