はじめに
@mylifewithviolinさんの投稿 Mind9βでSQLServer、Mysql、PostgreSQLからのSELECT結果を構造体格納の同氏によるコメントでMindの文字列データ、文字列系変数の詳細についてご質問をいただきました。
回答が長くなることと、他の閲覧者さんへの情報提供も兼ね、同投稿へのコメントではなく独立記事でまとめました。
ソースコードに文字列変数が書かれていたときのコンパイラの挙動
最初にちょっと遠回りします。
まず、文字列変数(文字列情報、文字列実体変数)をソース上に書いたとき、コンパイラはどうするかです。
たとえば、
住所1は 文字列実体 長さ 100桁。
住所2は 文字列。
のように2種類の形式で変数宣言されたものがあったとし、別の個所でそれを引用し、単に
住所1
または
住所2
などと書いたときは、コンパイラはその変数に格納されている文字列の文字列情報をスタックに積みます。おおざっぱに”変数の内容をスタックに積む”という言い方をしますが、厳密には変数内容そのものを積むわけではない点に注目してください。
住所1と住所2は変数の型が違いますが、参照時には統一された形式のデータ(=文字列情報)がスタックに積まれます。一旦スタックに積まれてしまえば、変数の型がどうだったかは関係なくなります。
この例のように他のプログラム言語とは違い、
Mindでの変数のデータ型とスタック上のデータ
形式は必ずしも一致しません。
この ”スタックに積まれた文字列情報”が、後続する処理単語に与えられることになります。
文字列情報とは、文字列の先頭アドレスと文字列
の長さをパックしたものです。32bitのMindでは
合計で64bit長になります。64bitはMindのスタッ
幅でもあります。
たとえば、
住所1を 表示し
と書くと、住所1の文字列情報がスタックに積まれ、次いで「表示」という処理単語が呼び出されます。
(コード生成)
「住所1」の変数内容をスタックにプッシュ
「一文字削除」を呼び出す
次に、文字列変数に副作用を与えるような処理単語を呼び出すときですが、たとえば、
住所1を 一文字削除し
と書いた場合のことを考えます。「一文字削除」は文字列変数の先頭の1文字を削除する機能として定義されています。
上記ソースを次のようにコンパイラが生成したとします。
(誤ったコード生成)
「住所1」の変数内容をスタックにプッシュ
「一文字削除」を呼び出す
のように普通の手続きを踏んだのでは、処理単語「一文字削除」は、変数「住所1」に副作用を与えることができません。住所1の”内容”を渡されても何もできないからです。変数に副作用を与えるには、変数のアドレスなど、変数の存在場所の情報が必要です。
ではなぜ、
住所1を 一文字削除し
と書くことで一文字の削除が正しく行われるかと言うと、このケースではコンパイラは「住所1」と書かれた部分をコンパイルする際、「住所1」の変数自体に関する情報(たとえば変数のアドレス)をスタックに積みます。これにより、処理単語「一文字削除」はスタックから渡された情報から副作用を与えるべき変数のアドレスなど、変数の情報を得て、削除処理をおこなうことができます。
ここで重要なのは、
値がPUSHされる
↓
住所1を 表示し
変数アドレスがPUSHされる
↓
住所1を 一文字削除し
のどちらであっても「住所1」という記述に違いが無いことです。
もしC言語であれば、
display( address1 );
delete1char( &address1 );
↑注:変数名の前に & を付ける
のように変数を記述する個所に違いが出るところです。
ひるがえって、Mindではどらちのタイプであっても変数名の記述は同じです。
Mindは「コンパイラが面倒な処理をする代わりに、ユーザは楽をする」という発想で作られていて、「後続する処理がその変数に副作用を与えるタイプかどうかを問わず、変数の記述部は同じにする方針で作られています。
その代わり、処理単語の側に特殊な属性を付け、変数への副作用を与える属性が付いた処理単語の呼び出し直前に書かれた変数表記については、その変数の内容ではなく変数自体に関する情報(たとえば変数アドレスなど)をスタックに積むようにしています。
実は、処理単語定義に特殊な属性を与えるための
ルールはMind8付属のマニュアルにも書かれてい
ません。
上級編のマニュアルで書くつもりだったのですが
いまだに未整備です。
文字列変数に副作用を与えるような処理単語、たとえば、
一文字削除
削除
切り出し
といったものはすべて標準ライブラリ内で定義済みであるため、こられを使う範囲内であれば、ここまで書いたことはユーザは知らなくてもプログラムが組めます。
ただ、@mylifewithviolinさんのように、ユーザさんがそのような処理単語(文字列変数に副作用を与える処理単語)を定義されたい方もおられるでしょうからマニュアル整備は必要だと思っています。
文字列変数のメモリ配置とスタック内の文字列データのメモリ配置
前置きが長くなりましたが、@mylifewithviolinさんのご質問であるところの、文字列データは実装上は(メモリ上は)どうなっているかをお話します。
住所1は 文字列実体 長さ 100桁。
住所2は 文字列。
上記を例とします。メモリ上の配置は以下の通りです。
[住所1(文字列実体変数)](64ビット)
<--------------------- 32bit --------------------->
・--------------------・
メモリ+0| カウント |
・--------------------・
+4| オフセット |
・--------------------・
+8| 格納された文字列 |
| ・・・・・・・ |
| ・・・・・・・ |
・--------------------・
上記の変数先頭から8バイトの部分を「文字列変数のヘッダ部」と呼んでいます。
ヘッダ情報の1つ「カウント」は格納されている文字列(の実体)の長さ(バイト数)を記憶します。
もう1つの「オフセット」は、文字列格納域に存在する文字列の格納域先頭から文字列先頭までの距離です。通常はゼロ。
「一文字削除」「削除」「単語切り出し」などをおこなうと、カウントが減少し、オフセットは増加します。つまり、「文字列変数のヘッダ部にあるカウントを減少し、オフセットを増加する」ことが文字列の削除の実態です。
たとえ「削除」をおこなっても、このようなヘッダ部の更新だけで済むため、格納されている文字列自体は変化しません。
[住所2(文字列情報変数)](64ビット)
<--------------------- 32bit --------------------->
・--------------------・
メモリ+0| カウント |
・--------------------・
+4| アドレス |
・--------------------・
文字列情報変数のサイズは8バイト固定ですが、このヘッダ部は文字列実体変数のそれとよく似ています。
文字列情報変数とは、文字列の実体はどこか別のところにあり、そこを指す情報のみを保持するものです。したがって文字列の実体はここにはありません。
ヘッダ部は文字列実体変数とよく似ています。違うのは、実体変数の場合は+4の位置にオフセットが記入されるのに比べ、情報変数の場合は同じ位置にアドレスが記入される点です。
さて、前記した文字列実体変数にしても、文字列情報変数にしても、それらの変数名をソース上に書くことで変数の値を参照した場合(変数内容をスタックにプッシュする場合)は次のようになります。
[スタックに積まれた文字列情報](64ビット)
<--------------------- 32bit --------------------->
・--------------------・
メモリ+0| カウント |
・--------------------・
+4| アドレス |
・--------------------・
スタックに積まれてしまえばもはや、参照元であった変数が文字列実体変数だったのか、文字列情報変数だったのかは区別つかなくなります。
(次に続く)