このブログ記事では、Pointers とreferences の定義、利点と欠点、どのアプリケーションに最適かについて簡単に説明します。
Pointers とreferences は、どちらもTwinCAT 3の強力な機能で、効率的なメモリ管理とデータ操作ができます。しかし、両者には明確な違いがあり、コードを設計・実装する際に理解しておく必要があります。
さ、FAを楽しもう。
Reference Video
Why Point/Reference?
Pointers とreferences は、コンピュータ上のアドレスを保持するのに必要なバイト数だけ格納されます。そのため、Point/Referenceは、それが指し示すものよりもずっと小さくなります。データを保存するときや関数にパラメータを渡すときには、この小さなサイズを利用する方がはるかに高速で、メモリ効率も良いのです。
Something about Pointer..
- Pointerは、他の変数のメモリー位置を保持する変数である。
- Pointer変数は、ポインタ記号 ‘^’を先頭に持つポインタ変数に格納されたアドレスにある値を返します。
- 演算子は ^・ ADR()
- ポインタ変数はNULLを参照できます。
- 初期化されていないポインターを作成することができる。
- ポインタ変数はプログラムのどの時点でも初期化できる。
- ポインタ変数は必要なだけ何度でも再初期化できる。
- ポインタが64ビット・ポインタであれば、64ビット・プラットフォーム上でも、2つのポインタの差の結果はDWORD型になります。
- UNCTION チェックポインタでできます。
Pointer Checks (POU CheckPointer)
実行時にPointerのメモリ・アクセスを監視するには、このPOU CheckPointer関数を使用できます。注意するのはCheckPointerにはデフォルトの実装がなく、ユーザー自身が実装を行う必要があります。
CheckPointer関数の目的は:
- 転送されたPointerが有効なメモリ・アドレスを指しているかどうか?
- 参照されたメモリ領域の向きがポインタの指す変数の型と一致しているかどうか?
この2つのPointをチェックすることです。両方の条件が満たされれば、Pointerそのものが返される。そうでない場合、この関数は適切なエラー処理を作成する必要があります。
また、無効なポインタを使用することによって、このポインタを介してアクセスが行われるとすぐにランタイムが停止します。例えCheckPointer関数を使っても、これを防ぐことはできず、効率的な原因診断を行うのが目的です。
Advantages?
- null値の可能性: ポインターはNull値を設定できるが、Referenceはできない。これにより、変数が存在する場合と存在しない場合がある状況で、より柔軟な対応が可能になる。
- Aliasing: Referenceは特定の変数にバインドされ、再割り当てができないので、複雑なデータ構造を扱う場合や、複数の変数に対して同時に演算を行う場合に便利です。
- Dynamic memory allocation: Pointerは動的にメモリーを割り当てることができるが、Referenceはできない。これにより、必要なメモリ量がコンパイル時にわからないような状況でも、より動的で柔軟なメモリの使用が可能になります。
- 大きなデータ構造を関数に参照渡しするには、ポインタの方が効率的である。
- Pointerは、リンクリストやツリーのようなリンクされたデータ構造を作るのにも使えますが、Referenceではできません。
Example
最初にPointerの理解を深めるために、いくつかのプログラム例を紹介します。
pPointer
こちらのExampleでは簡単なPointer操作を紹介します。各変数をADR関数経由で変数のアドレスを取得し、現在値を変更します。
PROGRAM pPointer VAR myiPointer: POINTER TO INT; myrPointer: POINTER TO REAL; mydiPointer: POINTER TO DINT; _myInt,myInt:INT; _myReal,myReal:REAL; _myDint,myDint:DINT; END_VAR // myiPointer:=ADR(myInt); myiPointer^:=_myInt; myrPointer:=ADR(myReal); myrPointer^:=_myReal; mydiPointer:=ADR(myDint); mydiPointer^:=_myDint; |
Done!
pPointerExample2
こちらは例はPointerが異なるタイプの変数に設定したときの動作を説明します。
PROGRAM pPointerExample2VAR a,b,c:INT; myiPointer:POINTER TO INT; myiPointerValue:INT; myiPointerValue2:INT; testRealValue:REAL:=3.14;END_VAR //init the values a:=1; b:=2; c:=3; //Pointer in Normal Operation myiPointer:=ADR(a); //Assign MyiPointer -> a myiPointer^:=b; //set a as value “b” myiPointerValue:=myiPointer^; //so, the finally value =2 //Pointer in difference Data type myiPointer:=ADR(testRealValue); myiPointerValue2:=myiPointer^; |
下図のように、MyiPointerは実数の変数にAssignされたせいで、myiPointerValue2が意味のない値にしか読み取れなかったです。
pPointerExample3
次はDynamic配列の作成例を紹介します。
PROGRAM pPointerExample3 VAR DynamicArray : POINTER TO INT; myValue : INT; i : INT; END_VAR //Allocate memory for the array DynamicArray := __NEW(INT, 10); //init the value FOR i :=0 TO 9 DO DynamicArray[i]:=(i+1)*i; END_FOR //show the value FOR i:=0 TO 9 DO myValue:=DynamicArray[i]; END_FOR //Deallocate the memory __DELETE(DynamicArray); |
下図のように、__NEW関数を利用しダイナミックの配列を生成し、そして最後は__DELETE関数を使用しメモリを開放します。
pPointerExample4
次のPointer例ではFOR LOOPを使用せず、配列のElementをアクセスします。
PROGRAM pPointerExample4 VAR inArray:ARRAY [0..6]OF INT:=[1,2,3,4,5,6]; myPointerStart:POINTER TO INT:=ADR(inArray); myPointerItem:POINTER TO INT; myValue:INT; bINC:BOOL; i:INT:=1; END_VAR IF bINC THEN myPointerItem:=myPointerStart+(i-1)*SIZEOF(INT); myValue:=myPointerItem^; i:=i+1; IF i >= 7 THEN i:=1; END_IF bINC:=FALSE; END_IF |
下図のように、myPointerStart+(i-1)*SIZEOF(INT)を使用し配列内のElementのアドレスを取得でき、myPointerItemでPointerに格納されたメモリ番地の現在値をアクセスします。
pPointer5FaultTest
最後はPointerをCheckするプログラムを作成します。POUS>右クリック>Add>POU for Implicit checksをクリックします。
Pointer ChecksにCheckboxを入れ、AddでImplicit checksプログラムを追加します。
こちらのプログラムがTwinCAT3自動生成なプログラムなので、実際のアプリケーションに合わせてプログラムを修正しましょう。
そして初期化さていないPointerをアクセスプログラムを作成します。
PROGRAM pPointer5FaultTest VAR myPointer:POINTER TO INT; myValue:INT; bFaultTest:BOOL; END_VAR IF bFaultTest THEN myValue:=myPointer^; bFaultTest:=FALSE; END_IF |
下図のように、プログラムを実行するとTwinCAT3 Runtimeがエラーになりました。
そしてError Listに”CheckPointer failed due to invalid address”のエラーメッセージが表示されました。
このメッセージが出力されたのは、Check PointerのPOUのプログラムのせいです。
Something about Reference..
- Reference変数は、その参照変数に代入されている変数を参照するために使われる。
- 参照変数は、変数のアドレスを返します。
- 演算子はREF= または :=になります。
- 参照変数は NULL を参照することはできません。
- 参照変数は、作成時にのみ初期化できます。
- 参照変数は、プログラム内で再度初期化することはできません。
- チェック関数:演算子 __ISVALIDREF を使用する。
__ISVALIDREF
__ISVALIDREF を使用すると、Referenceが有効な値、すなわち 0 ではない値を指しているかどうかをチェックすることができます。
Advantages?
- 使いやすくなった: 参照されているオブジェクトの内容にアクセスするために、参照を(^で)解除する必要がない。
- 値を転送する構文がすっきりした: 入力が 「REFERENCE TO 」の場合、(refInput := value)でADR(value)と書く必要はない。
- 型の安全性: ポインタとは対照的に、参照の場合、コンパイラは2つの参照を代入するときにベース型が同じかどうかをチェックします。
Example
次にReferenceの理解を深めるために、簡単なプログラム例を紹介します。
pRef
こちらのプログラムはmyIReferenceをINTタイプの変数myIntに参照します。
そして_myIntの現在点を変更すると、myIntに反映されます。
PROGRAM pRef VAR myIReference:REFERENCE TO INT; myrReference: REFERENCE TO REAL; mydiReference: REFERENCE TO DINT; _myInt,myInt:INT; _myReal,myReal:REAL; _myDint,myDint:DINT; END_VAR myIReference REF= myInt; myIReference:=_myInt; |
Finally…
PointerはReferenceよりもはるかに強力だが、使用する際の安全性は低いんです。
- Pointerを使えば、ダイナミック・ストレージや算術計算など、より多くのことができる。
- Pointerは型に関係しないので、スティングで計算するときに便利です。
- PointerはReferenceよりもエラーが起きやすい
- Pointerは不適切に参照される可能性があり、未定義の動作やメモリアクセス違反につながるからです。
- また、ReferenceはNULLにできず、コンパイル時に型がわかるので、より安全に使用できます。