Windows APIを使うとなると、普段のHSPプログラミングではあまり考慮されないことにも関わっていくことになります。その一つが『メモリ』に関することです。
プログラム上でデータを保持する際、そのデータは『メモリ』と呼ばれる記憶領域に格納される、ということはご存知ですよね。コンピュータの内部では、それらデータを格納したメモリブロックを指定するのに、その領域の場所を表す数値(番地)が使われます。この番地のことを『メモリアドレス』と呼びます。または、単に『アドレス』と呼ばれることも多いです。
HSPでは『変数』というものが使われていますよね。もちろん、HSPに限らず、さまざまなプログラミング言語で変数が使われています。変数とは「数値や文字列などのデータを自由に入れておくことができる箱(容器)のようなもの」というのが一般的な説明であると思います。しかし実際には、「ある特定のアドレスで示されるメモリ領域に付けられた名前」であるというほうがより近いのです。
プログラムをマシン語のレベルで考えたとき、メモリ内にデータを保持するためにはアドレスを直接指定しなければなりません。たとえば、
メモリの1000番地に数値 33を入れる メモリの3000番地に数値1242を入れる
というように処理をしていかなければならないのです。しかし、プログラミングをする上で数々のデータを保持しなければならないとき、それらのデータを入れておいたアドレス(番地)を覚えておいて、コードを書くたびにそのアドレスを指定する、などということをするのは非常に複雑かつ面倒な作業であるし、間違ったアドレスを指定してしまうなどのバグが頻発してしまうことでしょう。そこで、そのデータが格納されているアドレスにわかりやすい名前を付けて、その名前を使ってそのアドレスに格納されているデータを操作する、という方法がとられるようになりました。そのアドレスに付けられた名前が変数というわけです。これにより、プログラマは自分でアドレスを管理する必要がなくなったのです。
さて、変数という便利なものがあるのだから、メモリアドレスを意識する必要はなくなったのかな~、などと思ったらそれは間違いです。プログラミングをする上ではメモリアドレスというものは非常に重要かつ必要なものなのです。
そこでC言語などのプログラミング言語では「アドレスを格納するための変数」として、『ポインタ』というものが採用されています。こういった言語の中では、原則として、ポインタには通常の数値を代入することはできず、メモリアドレスのみを代入できるのです。(厳密に言えばアドレスも「番地」である以上は数値なんですけどね。)
残念ながら、HSPではアドレス専用の変数である「ポインタ」の概念は採用されていません。しかし、現在のHSP3では、以前のバージョンのもの比べると、比較的簡単にアドレスによるメモリ操作ができるようになっています。任意の変数のアドレスを取得する関数や、任意のアドレスが指すメモリブロックをHSPの変数領域として割り当てる命令が新規に追加されており、それらを使用することで簡単にメモリ操作ができるのです。これについては、後で詳しく述べることにします。
上で述べたようにC言語での『ポインタ』とは本来「アドレスを格納する変数」のことなのですが、実際にはアドレス自体のことをポインタと呼ぶことも多いようです。これは、英語の“pointer”(ポインタ)が「指し示すもの」という意味であることから、「特定のメモリ領域を指し示すもの」=「メモリアドレス」であるという考えからきていると考えられます。また、C言語では「パラメータとして…へのポインタを指定」と「パラメータとして…のアドレスを指定」の2つが事実上同じなので、「ポインタ」と「アドレス」の区別があいまいになってしまっているのかもしれません。ですから、今後「ポインタ」という言葉が出てきたら、それは「アドレス」と同じ意味であると思ってもかまいません。
「コンピュータはデータを0と1のみで処理する」とか、「コンピュータは2進数の世界」というのを聞いたことがあるでしょう。なぜ2進数なのでしょう?
その理由は、コンピュータのデータのやりとりを細かく見ていくと、すべてはON(電気が流れる状態)とOFF(電気が流れない状態)の2つの状態によって成り立っているということにあります。そこで、このON/OFFの2つの状態を数値の0と1で表し、さらにこの最小データ(0か1か)の単位を『ビット(bit)』と定めました。たとえば、1ビットの整数値のデータといったら0か1になり、2ビットの整数値のデータといったら00, 01, 10, 11の4種類(符号なし10進数表示でそれぞれ0, 1, 2, 3)のいずれかになります。一般にはnビットのデータは、符号なし整数値の場合に0から(2n-1)までの数値を格納できます。(符号ありの場合は-2n-1から(2n-1-1)まで。)
さらに、8ビットの数値を1つのまとまりとしてこのデータのサイズの単位を『バイト(byte)』としました。一般的なコンピュータは1バイト=8ビットをデータの基本単位としています。1バイトのデータは、符号なし整数値の場合0~255の数値を表現できます。(符号あり整数では-128~127です。)
さて、HSPで使っている変数に代入されたデータも実際はあるアドレスで示されているメモリに格納されているわけです。メモリにどのような形で格納されるのかを見てみることにしましょう。
メモリ上でデータを考える場合は16進数を使うほうが簡単になります。というのも、16進数では2桁でちょうど1バイトの数値を表現できるからです。以下では16進数を使用しています。(0x??は値が16進数であることを示します。)
HSPで使われる数値の形式は「符号付き32ビット整数値」と呼ばれます。書いた字のごとく符号付きの(正負を含めた)データサイズが32ビット(4バイト)の整数ということです。実際の数値の範囲は-2147483648から2147483647までとなっています。
メモリ領域をバイト単位で区切って考えたとき、数値は下位バイトほどメモリアドレスの小さいほうに格納されることになっています。たとえば、変数aに代入された数値0x12345678(10進数では305419896)は以下のように格納されています。
a = 0x12345678
↓変数aのアドレスの示す位置 | |||||
… | 0x78 | 0x56 | 0x34 | 0x12 | … |
配列変数に数値を代入した場合、それらはメモリ領域に4バイトずつ格納されていきます。実際には以下のようになります。
a(0) = 0x11223344 a(1) = 0x55667788 a(2) = 0x99AABBCC
↓変数aのアドレスの示す位置 | |||||||||||||
… | 0x44 | 0x33 | 0x22 | 0x11 | 0x88 | 0x77 | 0x66 | 0x55 | 0xCC | 0xBB | 0xAA | 0x99 | … |
←a(0)のデータ領域→ | ←a(1)のデータ領域→ | ←a(2)のデータ領域→ |
何となくわかるでしょうか?
上記のように、より下位のバイトから順に格納されていく方式をリトルエンディアンと呼びます。これはIntel系CPUの特徴の1つです。別の種類のCPU(例えばMac系のマシンに搭載されるもの)には、上位バイトから格納されるもの(ビッグエンディアン)もあります。
HSPで使われる数値は4バイト単位で扱われていましたね。文字列の場合は数値とはやや扱いが異なります。文字列はメモリ上では「1バイト単位の数値の配列」として格納されています。
「文字コード(キャラクタコード)」という言葉を聞いたことがあるでしょうか。これはある特定の文字を表す整数値のことです。HSPでは文字を’’(シングルクォーテーション)で囲むことでその文字コードに変換することができます。たとえば
c = 'A'
とした場合、変数cには大文字「A」の文字コードである0x41(10進数で65)が代入されます。シングルクォーテーションで囲まれた文字は、あくまで数値として扱われます。(HSPの場合、シングルクォーテーションで囲むことで文字コードを取得できるのは、半角アルファベットや数字などのいわゆる1バイト文字(ASCII文字)のみに限ります。)
ある特定の文字コード体系を規定した一連の文字群のことを「文字セット(キャラクタセット)」と呼びます。例えば、英語圏では、アルファベット・数字やいくつかの記号などの7ビットコードからなるASCII文字にさらに通貨記号やアクセント記号付きアルファベットなどを加えたANSI文字セットという文字セットが使われています。ANSI文字セットは、1バイト文字コードの集まりです。また、日本語環境のWindowsでは、後で説明するシフトJIS文字セットという文字セットが使われています。
さて、1つの文字がどのように表されるのかが分かったので、次に文字列について考えてみましょう。ご存知のように、HSPではダブルクォーテーションで囲まれた文字並びが文字列として扱われます。はじめに述べましたが、変数に格納された文字列は「1バイト単位の数値の配列」として格納されており、その要素の1つ1つが文字コードに対応しているのです。例えば、
s = "abcdEFGH"
とした場合は、文字列はメモリ上では次のように格納されています。
↓変数sのアドレスの示す領域 | ||||||||||
… | 'a' | 'b' | 'c' | 'd' | 'E' | 'F' | 'G' | 'H' | 0x00 | … |
ここでは'a'は小文字「a」の文字コードである0x62(10進数で98)を示しています。他の文字についても同様です。
文字列を表す文字コードの配列の最後には必ず0x00(ゼロ)が入ります。これは文字列がここで終わっていることを示しています。これを終端文字あるいはヌル文字(null文字)と呼ぶことがあります。HSPでは実際に格納する文字列のサイズよりも少なくとも1バイト多く変数領域を確保しなければいけませんが、その理由はこのためです。(HSP3では変数サイズの自動拡張機能があるので、文字列の「プラス1バイト」則はあまり意識されないかもしれませんが。)
ところで、ANSI文字列は1バイト文字の文字コードの集まりであるといいましたが、アルファベットや数字、いくつかの記号だけならば1バイトで事足りるので、1文字を1バイトで表すのに何ら問題はありません。しかし、日本語で使われる仮名や漢字などの文字は数が多いため、1文字を1バイトコードに対応付けることはまず不可能ですよね。そこで、こういった文字は2バイト分のデータ領域を用いて表されます。日本語ではWindowsで使われるシフトJIS (Shift JIS)や、UNIXで使われる拡張UNIXコード (EUC)のうちの日本語EUC (EUC-JP)などがこれにあたります。シフトJIS文字セットでは、ASCII文字はANSI文字セットと同じく1バイトコードで、また、半角カタカナや半角記号も1バイトコードで、それ以外の文字については拡張2バイトコードで表しています。このような文字セットは、1バイト文字と2バイト文字が混在しているので、一般に「マルチバイト文字セット」と呼ばれています。(「MBCS」と略されることもあります。または「2バイト文字セット(DBCS)」と呼ばれることも。) また、そのような文字からなる文字列のことを「マルチバイト文字列」といいます。
最近のOSは大概、仮想メモリ機能を備えています。これは、仮想記憶装置(ハードディスクなど)の空き領域を、あたかも物理メモリであるかのように見せかける機能のことです。この機能により、実際にマシンに搭載されている物理RAMよりも多くのメモリを使用することができるようになっています。
一般に、マシンに搭載されているRAMに割り当てられるメモリに対して、ディスクスペース上に割り当てられるメモリを仮想メモリといいます。仮想メモリの実体は一般にハードディスク上のファイルとして存在しますが、このファイルはページングファイルと呼ばれています。すべての仮想メモリの内容がこのページングファイルに格納されています。(Windows 9x系では「スワップファイル」と呼ばれることもありますが、ここでは「ページングファイル」に統一しています。)
アプリケーション側の視点から見れば、仮想メモリの機能によって、アプリケーションが利用できるメモリ(物理ストレージなどと呼ばれる)の容量が増加しているように見えるわけです。例えば、マシンに64MバイトのRAMが搭載されていて、ハードディスクに100Mバイトのページングファイルがあるなら、アプリケーションからは、あたかも164Mバイトのメモリが存在するかのように見えるのです。
プログラムが実際にメモリに読み書きするためには、結局は、その目的となる領域が物理RAM上に存在しなくてはなりません。したがって、メモリアクセス時に、目的の領域がRAM上に存在しない場合には、いったん、ページングファイル上に存在するその内容をRAM上に読み込まなくてはならないわけです。
アクセスしようとしているデータが物理RAM上になく、ページングファイルのどこかに存在する場合には、ページフォールトと呼ばれる例外(通知の一種)が発生し、その通知を受けたOSは、物理RAM上からフリーページ(RAM上の使われていない部分)を探して、見つかったフリーページを割り当てます。フリーページが見つからない場合には、RAM上のどこかの部分をいったんページングファイル上に移し、その後で、その部分にアクセスするページを読み込む、といった動作をします。
RAMへのアクセスとハードディスクへのアクセスとを比べると、ハードディスクアクセスの方がはるかに低速であるため、このようなRAMとページングファイルとの間でのコピーが頻繁になると、それらの処理に多くのCPU時間を消費してしまい、システムが低速になってしまいます。このような状態をスラッシングと呼びます。一般的に、マシンのRAM搭載量を増やすことで、スラッシングを防ぐことができるので、システムの処理性能を向上させることができます。
ちなみに、HSPでは、sysinfo関数を使用することによって、物理RAMやページングファイルのサイズの情報を得ることができます。(ただし、物理RAMサイズまたはページングファイルサイズが2Gバイト以上の場合には、符号付き32ビット整数値の範囲を超えるため正常に表示されません。)
a = sysinfo(34) / (1024 * 1024) b = sysinfo(35) / (1024 * 1024) mes "物理 RAM サイズ : " + a + " MB (空き " + b + " MB)" a = sysinfo(36) / (1024 * 1024) b = sysinfo(37) / (1024 * 1024) mes "ページングファイルサイズ : " + a + " MB (空き " + b + " MB)"
Windowsは、新しいプロセス(実行されたプログラムのこと。正確には、メモリにロードされたプログラムの実体(インスタンス)のこと)が起動するときにそのプロセス用に4G(ギガ)バイトの仮想アドレス空間(論理アドレス空間とも言う)を用意し、その仮想アドレス空間にプロセスを生成します。簡単に言うと、すべてのプロセスは4Gバイトのメモリを割り当てる準備がされている、というわけです。
「あれ、僕のパソコンにはメモリは4Gバイトもないよ?」と、不思議に思う人もいるでしょう。大体、筆者も、4Gバイトものメモリを積んだパソコンなんて、今のところは見たことがありません(科学技術計算用のワークステーションならありますが…)。もちろん、アプリケーションが起動されたからって、4GバイトものメモリがRAMから確保されるわけじゃありませんし、先に述べた仮想メモリ機能を使って、4Gバイトものハードディスクスペースを消費してしまうということもありません。あくまで、「仮想的な」アドレス空間が用意されるのです。
仮想アドレス空間というのは、実際に存在する物理ストレージの大きさには関係なく、その名が示す通り「仮想的な」アドレス空間であり、それは単にメモリアドレスの範囲に過ぎないということです。32ビット変数は(16進数で)0x00000000~0xFFFFFFFFまでの任意の数値を格納することができますが、同様に32ビットポインタもこの範囲での任意の値(メモリアドレス)を格納することができます。したがって、32ビットポインタを使用する限り(32ビットWindowsでは必ずそうなるわけですが)、アドレス0x00000000~0xFFFFFFFFまでの4Gバイト範囲でメモリアドレスを指定することができる、すなわち、その範囲の仮想的な(あるいは論理的な)アドレス空間が存在している、ということができるのです。
実際にデータにアクセスするためには、仮想アドレス空間と実際のメモリ(物理ストレージ)との間の対応付けをする必要があります。この操作を、仮想アドレス空間に物理ストレージを割り当てる(マッピングする)と言います。特に、Windowsでは、この対応付けがそれぞれのプロセスごとに独立して行われます。「それぞれのプロセスが独自に4Gバイトの仮想アドレス空間を持つ」のはこのためです。
アプリケーション側からは、自身のプロセスの仮想アドレス空間しか見えません。通常の方法では、どのようなメモリアドレスを指定したとしても、(仮想アドレス空間でなく)物理ストレージ上の任意の位置のデータを読み込んだり、自分以外のほかのプロセスの仮想アドレス空間にあるデータを読み書きすることはできないのです。このため、一般に、単に「アドレス(メモリアドレス)」といった場合には、プロセスの仮想アドレス空間におけるアドレスを指します。
仮想アドレス空間内のデータにアクセスするためには、仮想アドレス空間と物理ストレージとの間の対応付け(マッピング)が必要だといいましたが、実際の動作はもう少し複雑です。Windowsでは、プロセスの仮想アドレス空間のそれぞれの領域に次の3つの状態を与えてそれらを管理しています。
アプリケーションプログラムの起動によってプロセスが作成されるとそのプロセスに仮想アドレス空間が与えられるのですが、その広大な空間のほとんどは確保されていない状態、すなわちフリー状態にあります。仮想アドレス空間のどこかの領域を使用するためには、その領域を確保しなければならないのです。この、仮想アドレス空間から領域を確保することを予約と呼びます。
しかし、仮想アドレス空間内の領域を予約(確保)しただけでは、まだ、その領域にアクセスすることができません。実際にアクセスするためには、先ほども述べた通り、予約された領域と同じ大きさの物理ストレージ(メモリ)を確保し、それを仮想アドレス空間の予約済み領域と対応付ける(物理ストレージを仮想アドレス空間の領域に割り当てる(マッピングする))ということをしなければなりません。この処理を、物理ストレージのコミットと呼びます。物理ストレージがコミットされていない仮想アドレス空間の領域にアクセスしようとすると、(たとえ予約されている領域でも)アクセス違反が発生して、プログラムが強制終了されてしまいます。
仮想アドレス空間の予約済み領域にコミットされた物理ストレージが不要になった場合には、物理ストレージ解放して、元の予約済みの状態に戻すことができます。(さらに、予約済み領域を解除して、フリー状態まで戻すこともできます。) こうなると、それまでその領域に格納されていたデータはなくなってしまいます。また、解放された物理ストレージ(仮想アドレス空間の領域ではなくて)は、他のプロセスからも使用できるようになります。
このような「領域の予約」という動作は、多少面倒に感じるかもしれませんね。Windowsがなぜこのような動作をする必要があるのかを少し考えてみることにしましょう。
そもそも、予約されている状態の領域は、以下のような特徴があるのです。
「予約」の概念が必要となるのは、非常に大きなメモリを要するアプリケーションを作成する場合です。ここでは、例として、大きなサイズ(それこそ、1Gバイトくらい)のテキストファイルを編集できるテキストエディタを考えましょう。
領域の予約というものが存在しないとしたら、アプリケーションを起動していきなり、1Gバイトの物理ストレージを割り当てる、ということになりますよね。すなわち、1Gバイトのテキストであろうが、数十Mバイトのテキストであろうが、はたまた、ほんの数バイト程度のテキストであろうが、そのテキストファイルを作成・編集するために、1Gバイト分のRAM(もしくはページングファイルのためのハードディスク容量)を消費してしまうというのです。それだけのRAMやハードディスク容量が存在しなければ、アプリケーションを実行することすらできない、ということになってしまいますね。
もちろん、サイズが大きくなるにしたがって、コミットする物理ストレージの大きさを少しずつ増やしていく、という方法もあります。現在の最大サイズを超えそうになったら、より大きいサイズの領域を確保して、内容をそちらの領域にコピーし、その後で元のメモリ領域を解放する、というものです。しかし、これでは必要以上の物理ストレージを消費してしまうことになります。例えば、領域を600Mバイトから800Mバイトへと拡張する際に、一時的にとはいえ、1400Mバイトの領域が使われてしまっていることがわかるでしょう。そもそも、再確保する際に、それだけの連続した(切れ切れになっていない)領域が仮想アドレス空間内に残っているのかどうかも怪しいものです(特にサイズが大きい場合には)。
予約という概念を導入することで、これらの問題は一気に解決します。アプリケーション起動時に、まず1Gバイトの領域を予約してしまうのです。こうすると、まず「仮想アドレス空間での連続した1Gバイトの領域」というものが保証されます。また、予約されているだけで、まだ物理ストレージをコミットしているわけではないので、実際に1GバイトものRAMやディスクスペースを消費するといったこともありません。テキストのサイズが増えていくにつれて、コミットするサイズを先頭から順に増やしていけばいいのです。(まあ、実際にテキストエディタを作成する場合には、アンドゥデータの管理とか、他にもいろいろ処理すべきことがあって、こう簡単にはいかないでしょうけどね……。)
32ビットWindowsでは4Gバイトの仮想アドレス空間が割り当てられると言いましたが、そのうちの上位2Gバイト(アドレス0x80000000~0xFFFFFFFF)はWindowsのシステム(カーネル)用に予約されているために、アプリケーションのプロセス側からアクセスすることはできません。したがって、プロセスは下位2Gバイトの領域を使用することになります。
さらに、Windowsはこの2Gバイトの領域(アドレス0x00000000~0x7FFFFFFF)のうち、先頭および末尾のいくらかの領域も予約しています。どのくらいの領域が予約されているのかは、Windowsカーネルが9xカーネルかNTカーネルかによって異なります。Windowsがこういった領域を予約しているからといっても、それらのうちのほとんどは物理ストレージがコミットされていない状態です。したがって、それらの領域のために物理ストレージが実際に消費されてわけではないということは、先ほど説明した通りです。
プロセスが起動されているとき、仮想アドレス内には実行ファイル(EXE)・システムDLL・アプリケーションDLLからロードされた実行可能プログラムコード、およびプログラムで使われているデータのブロックが配置されています。プログラムはここにロードされた実行可能コードにそって実行されていくことになります。
メモリアドレスを格納する変数として使用されるC/C++のポインタには「仮想アドレス空間のどこも指していない状態」というものが定義されており、そのようなポインタは「NULLポインタ(ヌルポインタ)」と呼ばれています。実際には、アドレス0(ゼロ)が格納されているポインタがNULLポインタとして扱われます。そのため、定数名NULLは、数値0で定義されています。
NULLを0と定義しているのなら、もし、仮想アドレス空間のアドレス0を指定したいときにはどうすればいいのだろう、と当然思うことでしょう。しかし、Windows APIを扱う上で、仮想アドレス空間のアドレス0を指定するということはありません。というのも、上で述べたように、Windowsは仮想アドレス空間のうちの先頭の領域をあらかじめ予約しており、アプリケーションがその領域を使えないようにしているためです。実はこれには、プログラムのバグなどで誤ってNULLポインタにアクセスしてしまったときに、例外(アクセス違反)を発生させることによって、バグの存在をプログラマに気付かせようという目的があるのです。
Windows APIには、パラメータとして、何らかのポインタ(メモリアドレス)の代わりにNULLを指定しなければならないものがあります。そのような場合には、パラメータに数値の0を指定すればOKです。
ちなみに、「null」は英語で「ナル」と発音するので、「ヌル文字」「ヌルポインタ」のことを日本語でも「ナル文字」「ナルポインタ」と呼ぶ場合があったりします。ですが、語源であるドイツ語では「ヌル」と読みますので、日本語でも「ヌル」で問題ありません。
Windows APIのいくらかの関数はその引数(パラメータ)としてメモリアドレス(ポインタ)を指定しなければならないものだったり、あるいは、関数の戻り値がメモリアドレスだったりします。これらのAPIで使用されているアドレスは、すべてプロセスの仮想アドレス空間上におけるメモリアドレスになっています。
HSP3では、メモリアドレスによるデータ操作をするための命令・関数として、varptr関数とdupptr命令が提供されています。メモリアドレスによる操作を必要とするAPI関数を使用するときに、それらの命令や関数が役に立つことでしょう。
varptr関数は、任意のHSP変数に格納されているデータのアドレス取得するものです。パラメータなどで、変数のメモリアドレスが必要となる場合に、この関数を使用することができます。
実際の使用方法としては、例えば
p = varptr(a) ; 変数 a のアドレスを取得する
のように書くことで、変数aのアドレスを変数pに格納することができます。
一方、dupptr命令は、任意のメモリアドレスのデータブロックをHSP変数として対応付けするもので、通常のHSPの変数に読み書きするのと同じ要領で、仮想アドレス空間における任意のアドレスのメモリブロックに対して読み書きを行うことができるようになります。
これらのパラメータのうち、n4は変数v1を何の型の変数として割り当てるかを指定するもので、省略するか4を指定した場合には整数型変数、3を指定した場合には実数型変数、2を指定した場合には文字列型変数として扱われます。(これらの型指定はvartype関数を使って得ることもできます。)
上記のスクリプトの続きとして、変数pに取得されたアドレスのメモリブロック(これは実際には変数aの領域ですが)を、変数tに割り当てることを考えてみましょう。
dim a, 16 a = 100 p = varptr(a) ; 変数 a のアドレスを取得する mes "変数 a のアドレスは 0x" + strf("%08x",p) + " です" dupptr t, p, 16 ; アドレス p の領域を変数 t として割り当てる mes "t = " + t ; アドレス p の領域のデータを読む
このスクリプトでは、dupptr命令にアドレスpを渡すことによって、そのアドレスが指すメモリブロックの16バイト分が、変数tに対応付けられます。このスクリプトではdupptr命令の第4パラメータを省略しているので、変数tは整数値型変数として扱われます。
1つ注意しておくべきことは、dupptr命令に渡すアドレスは有効なものでなくてはならないということです。上のスクリプトではもともと変数aの領域のアドレスを渡したので問題はないのですが、例えばアドレスとして数値0だとか100だとか適当な値を渡すと、仮想アドレス空間の中で物理ストレージが割り当てられていない(コミットされていない)領域にアクセスしてしまい、アクセス違反によって強制終了されてしまうことになります。