FreeBASIC マニュアルのトップに戻る

FreeBASIC ポインタ・データ型

目次→教本→いっしょに学ぼうThe Pointer Data Type←オリジナル・サイト

ポインタ・データ型 左にメニュー・フレームが表示されていない場合は、ここをクリックして下さい

←リンク元に戻る プログラム開発関連に戻る


ポインタとメモリ
型付きと型なしのポインタ
ポインタ演算子
メモリ関数
ポインタ演算とポインタ索引
ポインタ関数
サブルーチンと関数のポインタ
コールバック関数を生成する
ポインタへのポインタ

ポインタ・データ型は、FreeBasic の数値データ型の中で、優れたものです。
データを含む代わりに、他の数値型のように、ポインタは、データのメモリ・アドレスを含んでいます。

32ビット・システムでは、ポインター・データ型は、4バイトです(64-bit システムでは8バイト)。
FreeBasic は、ImageCreate などの多くの関数で、ポインタを使います。そして、ポインタは、Windows API などの外部ライブラリでも頻繁に使われます。
ポインタは、また非常に高速です。コンパイラは、ポインタが指すメモリ位置に直接アクセスできるからです。
ポインタを適切に理解することは、FreeBasic での効果的なプログラミングに、不可欠です。

多くの初心者のプログラマには、ポインタは、奇妙で神秘的な野獣のように見えます。
しかし、1つの規則を覚えておくと、あなたはプログラムでポインタを使うことに、問題がなくなるでしょう。
規則は、非常に簡単です:
ポインタは、データではなく、アドレスを含んでいます。
この簡単な規則を覚えておくと、あなたには、ポインタを使っても、なにも問題は、起きないでしょう。


ポインタとメモリ

あなたは、コンピュータのメモリを、地元の郵便局での、1セットの私書箱 (P.O. Box) と考えることができます。
あなたが、私書箱を賃借しようとすると、事務員は、100 などの数をあなたに与えるでしょう。
これは、あなたの私書箱の、アドレスです。
あなたは、メモ用紙に、この数を書きとめて、財布にそれを入れるでしょう。
翌日、あなたは、郵便局に行って、メモ用紙を取り出します。
あなたは、箱 100 の場所を見つけて、箱の中をのぞいて、郵便物が、何通か、入っているのを見つけます。
もちろん、ダイレクト・メールは捨てたいと思っていますが、適当なくず入れがないので、あなたは、箱の中にメールを戻して、後でそれを捨てることにします。
FreeBasic で、ポインタを使うのは、私書箱を使用するのと、とても似ています。

ポインタを宣言するとき、ポインタは、白紙の切れ端に類似していて、何も指していません。
ポインタを使うために、ポインタで、メモリアドレスの初期化をしなければなりません。(これは、メモ用紙の上にNo.100を書き留めるのと同じです)。
これで、あなたはアドレスを持って、正しい私書箱を見つけられます。あなたは、ポインターを逆参照できます。(ポインターの指す値を取得できます。) メールボックスを開けて、ポイントされている記憶域に対して、データを加えたり、データを検索したり、できます。
お分かりのように、ポインタを使うには、3つの基本的なステップがあります。

  1. ポインタ変数を宣言する。
  2. ポインタを、メモリアドレスに初期化する。
  3. ポインターを逆参照して、ポインターが示している記憶域でデータを操る。

これは、標準の変数を使うのと、少しも違いません。あなたは、ポインタを、大体同じようなやり方で、標準の変数として、使います。
ポインタと標準の変数の間の、唯一の本当の違いは、標準の変数では、あなたは、直接、データにアクセスできるということです。ポインタでは、あなたは、ポインターからデータに相互作用するために、ポインターを間接参照しなければなりません。


型付きと型なしのポインタ

FreeBasic には、型付きと型無しの、2種類のポインタがあります。
型付きポインタは、関連するデータ型を付けて、宣言されます。
Dim myPointer As Integer Ptr


これは、このポインタは、整数データに使用される、とコンパイラに伝えます。
型付きポインタを使うと、コンパイラは、あなたが、ポインタに、間違った型のデータを使用しないように、念のために型チェックをしてくれます。そして、ポインタ演算を簡素化します。

型無しポインタは、Any キーワードを使って宣言されます。
Dim myPointer As Any Ptr


型無しポインタは、型チェックをしなくて、デフォルトのサイズは、バイトです。
型無しポインタは、C ランタイム・ライブラリや、Win32 APIなどの、多くの第三者ライブラリで使用されます。C でボイド・ポインタ(不定の変数型を指すポインタ)型に対応するために。
あなたが特に型無しポインターを必要としない限り、コンパイラがポインター代入をチェックできるように、あなたは型付きポインターを使用すべきです。


ポインタ演算子

FreeBasicには、以下のポインタ演算子があります。

あなたは、addressof 演算子が、変数のメモリアドレスを返すだけではないのに気付くでしょう。addressof 演算子は、サブルーチンや関数のアドレスも、返すことができます。
CRT QSort 関数で使われるような、コールバック関数を作るために、サブルーチンや関数のアドレスを使います。


メモリ関数

FreeBasic には、また、ポインタと共に使用される、多くの メモリ関数 があります。

これらの関数は、多くの動的プログラム構造を作成することに役立ちます。動的構造とは、連結リストや、不規則の、あるいは、動的な配列や、第三者ライブラリと共に使用されるバッファなどです。

Allocate 関数を使う場合は、式 number_of_elements * SizeOf(データ型) を使って、データ型に基づいた記憶域サイズを指定する必要があります。
10個の整数にスペースを割り当てるには、次のようにします。
myPointer = Allocate(10 * SizeOf(Integer))
整数は、4/8バイト(32/64 bit システムで)です。したがって、10個の整数を割り当てると、40/80バイトのメモリ(32/64bit システムで)を確保します。
Allocate はメモリ・セグメントをクリアしないため、セグメント内のデータは、初期化されるまで無意味なデータになります。

Callocate は、同じやり方で働きます。ただし、計算は内部的にされます。
同じ 10 の整数を、Callocate を使って割り当てるコードは、下のようになります:
myPointer = Callocate(10, SizeOf(Integer)).
Allocate と異なって、Callocate は、メモリ・セグメントを、クリアしまず。

Reallocate は、既存のメモリ・セグメントのサイズを変えます。必要により、メモリ・セグメントのサイズを、より大くか、より小さくします。
新しいセグメントが、既存のセグメントより大きいなら、既存のセグメントのデータは、保存されます。
新しいセグメントが、既存のセグメントより小さいなら、既存のセグメントのデータは、先端を切られます。
Reallocate は、追加されたメモリをクリアしません。また、既存のデータを変えません。

うまくいくと、これらの関数のすべては、メモリ・アドレスを返します。
関数が、メモリ・セグメントを割り当てることができないときは、NULLポインタ (0) を返します。
これらの関数を使用するたびに、戻り値をチェックするべきです。メモリ・セグメントが、首尾よく作成されたかどうか、確信できます。
悪いポインタを使おうとすると、好ましくない行動か、システムクラッシュが、起こります。

割り当てるサイズを決定するための、固有の方法はありません。
あなたは、自分で、この情報の経過をおさえなければなりません。

複数のメモリ・セグメントを割り当てるのに、同じポインタ変数を使用しないように、注意してください。
最初に、ポインタが指している、セグメントを「割り当てを解除せ」ずにポインタを再利用すると、メモリ・リークを引き起こして、メモリ・セグメントは失われるでしょう。


ポインタ演算とポインタ索引

allocation 関数を使って、メモリ・セグメントを作成するとき、セグメントの中に含まれたデータにアクセスする手段が、必要になります。
FreeBasic には、セグメントの中のデータにアクセスするための、2つの方法があります。
ポインター算数で間接演算子を利用することと、ポインター索引です。

ポインタ演算は、名前が示すように、値をポインタに、加算、減算して、メモリ・セグメントの中で、個々の要素にアクセスします。
型付きポインタを、 Dim myPointer as Integer ptr などのようにして作成するとき、コンパイラは、このポインタで使用されるデータが、サイズ Integer か、4 バイトであることを知っています。
ポインタは、初期化されると、セグメントの最初の要素を示します。
これを、 *(myPtr + 0) と表現できます。
2番目の要素にアクセスするのに、ポインタに 1 を加える必要があります。 *(myPtr + 1) と表現できます。

コンパイラは、ポインタが Integer(整数) ポインタであることを認識しているので、ポインタ参照に 1 を加えると、実際には myPtr に含まれるアドレスが、4/8(32/64bit システムで)、Integer のサイズだけ増加されます。
これが、型指定のポインタを使う方が、型なしのポインタを使うよりも好ましい理由です。
コンパイラは、メモリ・セグメント内のデータにアクセスするために、多くの作業を行います。

構築は、 *(myPtr + 1) で、 *myPtr + 1 ではないことに注意してください。
* 演算子は、+ より優先順位が高いので、*myPtr + 1 は、ポインタアドレスではなく、実際に myPtr が示すコンテンツを増加させます。

ポインタ索引は、ポインタ演算と同じように働いています。しかし、詳細はコンパイラによって扱われます。
*(myPtr + 1) は、myPtr[1] と同等です。
一方、コンパイラは、myPtr が整数ポインタであることを知っているので、インデックスを使って、正しいメモリ・オフセットを計算して、固有値を返すことができます。
あなたが使う形式は、あなたしだいです。しかし、索引の方法は、標準的な配列アクセスの方式に似ています。そして、視覚的に、間接演算子より、理解しやすいでしょう。


ポインタ関数

Freebasic には、ポインタ演算子の補足となる、ポインタ関数のセットがあります。

Sadd と Strptr 関数は、文字列データのアドレスを返すために、文字列データ型を使います。
Peek と Poke 関数はレガシー・コードをサポートする目的で、加えられています。
Procptr と Varptr の両方は、まさしく、演算子@ のアドレスのように働きます。しかし、Proptr は、サブルーチンと関数にだけ、取り組みます。そして、Varptr は、変数にだけ、取り組みます。
Cptr は、型の無いポインタを、型のあるポインタに、型変換するために、役に立ちます。例えば、第三者ライブラリからの戻り値などにです。


サブルーチンと関数のポインタ


サブルーチンと関数は、変数のように、メモリに住んでいます。そして、これらの開始位置(エントリーポイント:呼び出し位置)に関連しているアドレスを持っています。
これらのアドレスを、プログラムでイベントを作るために使えます。また、擬似オブジェクトを作るために使えます。また、コール・バック関数として、使うことができます。
あなたは、他のいかなるポインタとおなじように、sub や function のポインタを作成します。変数を、データ型へのポインターとしてではなく、サブルーチンや関数へのポインターとして宣言する点が、違うだけです。

関数ポインタを使う前に、関数ポインタは、Procptr か 演算子@ を使って、サブルーチンか関数のアドレスに、初期化しなければなりません。一度初期化されると、元のサブルーチンや関数を呼ぶのと同じ方法で、ポインタを使います。

匿名の宣言構文を使って、関数ポインタを宣言します。

Dim FuncPtr As Function(x As Integer, y As Integer) As Integer


そして、この関数ポインターを、コード内の実際のサブルーチンか関数に、関連させる必要があります。

Function Power(number As Integer, pwr As Integer) As Integer
    Return number^pwr
End Function

FuncPtr = @Power


これで、実際の関数を呼ぶように、関数ポインタを呼ぶことができます。

FuncPtr (2, 4)


これは、一見、役に立たないようにみえますが、この技術は、多形関数を実装するために、使用できます。多形関数では、一つの変数インスタンスが、いくつかの異なるサブルーチンまたは関数のうちの1つを示すことができます。

例えば、犬と猫のオブジェクトを持っていると仮定してください。
両方のオブジェクトは、鳴き方必要とします。
関数能ポインタとして Speak を定義して、Speak を、犬のためのワンワン・サブルーチンと猫のためのニャーオ・サブルーチンに関連付けることにより、オブジェクト型に従って、Speak に「ワン!」か「ニャオ!」を出力させることができます。


コールバック関数を生成する


「コールバック」とは、電話を相手に一度かけて電話番号のみを伝え、いったん電話を切って、折り返し相手からかけなおしてもらうこと、をいいます。
関数ポインタに対する主要な用途の1つは、コールバック関数を作成することです。
コールバック関数は、あなたのプログラムで作成した関数です。この関数は、あなた自身のコードの中か、外部のライブラリの中で、もう一つの関数またはサブルーチンによって呼ばれます。
Windows は、字体、プリンタ、用紙のような、Window オブジェクトを通して列挙するために、コールバック関数を使います。

C ランタイム・ライブラリ内に含まれている関数、qsort は、ソート順序を決定するために、コールバック関数を使って、配列の要素をソートします。
qsort 関数 のための原型は、crt/stdlib.bi に含まれています:

Declare Sub qsort (ByVal As Any Ptr, ByVal As size_t, ByVal As size_t, ByVal As Function(ByVal As Any Ptr, ByVal As Any Ptr) As Long)


下は、qsort サブルーチンのためのパラメタ情報のリストです。

  1. 最初のパラメタは、配列の最初の要素のアドレスです。
    この情報を qsort に渡す最も簡単な方法は、最初の要素インデックスに、演算子のアドレスを追加することです:
    @myArray(0).
  2. 2番目のパラメタは、配列の要素数です。すなわち、配列カウントです。
  3. 3番目のパラメタは、バイトで表現される、各要素のサイズです。
    整数の配列では、要素サイズは 4バイトです。
  4. 4番目のパラメタは、ユーザ作成の比較関数への関数ポインタです。
    関数は、Cdecl 渡しモデルを使って宣言しなければなりません。このパラメタに示されているようにです。

この情報を使って、qsort がどのように働いているかを見ることができます。
要素の数とともに、最初の要素のアドレス、および各要素のサイズを渡すことによって、 qsort は、ポインター算数を使って、配列を終わりまで、繰り返すことができます。

Qsort は、配列要素の 2つを取って、それらをユーザ定義の比較関数に渡して、比較関数の戻り値を使って、配列の要素をソートします。
Qsort は、配列の各要素が、ソートされた順番になるまで、繰り返これをします。

あなたは、Cdecl として関数原型を宣言する必要があります。Cdecl は、パラメタが、正しい順番で渡されることを確実にします。

Declare Function QCompare Cdecl (ByVal e1 As Any Ptr, ByVal e2 As Any Ptr) As Long


そして、下のように関数を定義します。

'qsort 関数は、
'compare 関数からの3つの数値を期待しています:
'-1: e1 が e2 未満のとき
'0: e1 が e2 と等しいとき
'1: e1 が e2 より大きいとき

Function QCompare CDecl (ByVal e1 As Any Ptr, ByVal e2 As Any Ptr) As Long
    Dim As Integer el1, el2
    Static cnt As Integer

    '呼び回数と、渡した項目を取得
    cnt += 1
    '整数ptrに型変換する必要がある値を取得
    el1 = *(CPtr(Integer Ptr, e1))
    el2 = *(CPtr(Integer Ptr, e2))
    Print "Qsort called";cnt;" time(s) with";el1;" and";el2;"."
    '値を比較
    If el1 < el2 Then
        Return -1
    ElseIf el1 > el2 Then
        Return 1
    Else
        Return 0
    End If
End Function



そして、コールバック関数のアドレスを渡して、QSort 関数を呼びます。

#include "crt/stdlib.bi"

qsort @myArray(0), 10, SizeOf(Integer), @QCompare



ポインタへのポインタ


FreeBasic では、ポインタ・データ型を含む、サポートされたデータ型のいずれにも、ポインタを作成できます。
ポインタへのポインタは、ポインタを関数に返す必要がある場合や、連結リストや不調和配列(配列要素数の終端が一様でない配列)などの、特別のデータ構造を作る場合に、役に立ちます。
ポインタへのポインタは、マルチレベル間接指定と呼ばれます。

ポインタへのポインタの活用方法の 1つは、まさしく配列のように振る舞う、メモリセグメントを作ることです。
例えば、未知の数の整数を保持するために、メモリセグメントを作成したいと仮定してください。
あなたは、動的メモリ・セグメントを作成できます。動的メモリ・セグメントは、必要とするだけの整数を扱うために、実行時に必要に応じて、サイズを変更することができます。
pointer-to-pointer 変数を作成することによって、開始します。

Dim myMemArray As Integer Ptr Ptr


そして、Allocate か Callocate を使って、ポインタ参照を初期化します。

'10 行の整数ポインタを作成します
myMemArray = CAllocate (10, SizeOf(Integer Ptr))


変数は、Integer Ptr に初期化されることに注意してください。このリストが、別のリストを示すからです。
これは、別のポインタを示すポインタです。
そして、必要なメモリ・セグメントを示すためにちょうど作成された、個別のポインタ参照を、初期化できます。

'各行に、10 列の整数を加えます
For i As Integer = 0 To 9
    myMemArray[i] = CAllocate(10, SizeOf(Integer))
Next



下の短いコードでは、リストの個々のポインタは、実際の整数データを含む、10のメモリ・セグメントに、初期化されます。

'メモリ・セグメントにデータを追加
For i As Integer = 0 To 9
    For j As Integer = 0 To 9
        myMemArray[i][j] = Int(Rnd * 10)
    Next
Next


この短いコードは、実際のデータを、メモリ・セグメントに登録するために、添え字(索引)方式を使います。
これは、まさしく 2次元配列のように見えて、振舞うことに、注意してください。
これは、この文脈では、役に立たないように見えるかもしれませんが、型定義の中で、動的な配列を作るときに、このコードを使用できます。
型の中で、標準の動的な配列を持つことができないので、これで、同じ機能性を得ることができるようにします。

編集者注:動的配列は、FreeBASIC の現在のバージョンでは許可されています。

知っておく必要がある大切なことは、このような構造を解放する方法です。
規則は、ただメモリを割り当てた操作の、逆の順番でするということです。
メモリ割付操作の最後は、データメモリ・セグメントを初期化することでした。このため、最初に、このメモリ・セグメントの割当てを取り消します。そして、ベース・ポインターの割当てを取り消すことができます。

'メモリ・セグメントを解放
For i As Integer = 0 To 9
    Deallocate myMemArray[i]
Next

'ポインタをポインタに解放
Deallocate myMemArray


あなたは、正しい順番で「割り当て解除」する必要があります。さもないと、メモリ・セグメントは解放されないで、アクセスできない状態で終わります。
これは、メモリ・リークで、プログラムで、多くの問題を引き起こす場合があります。

sancho3 が、2018年2月7日に修正しました。

いっしょに学ぼう に戻る
←リンク元に戻る プログラム開発関連に戻る

ページ歴史:2018-02-11 11:31:06
日本語翻訳:WATANABE Makoto、原文著作者:WikiRick(Rick Clark aka rdc)

ホームページのトップに戻る

表示-非営利-継承