本章で推奨するガイドラインは,難しく厳密な規則ではありません。 適切なプログラミングの例を紹介しながら,ガイドラインを説明します。
Cポインタ・プラグマについての詳細は, 『DEC C User's Guide for OpenVMS Systems』を参照してください。
たとえば64ビット・アドレスを,誤って 32ビット・アドレスしか処理できないルーチンに渡す可能性が考えられます。また, 新しいAPIが64ビット・ポインタをデータ構造に埋め込む場合も考えられます。 このようなポインタは,新しいデータ構造の中では符号拡張の 32ビット値として存在しますが,最初は 32ビット・アドレス空間のポイントに制限されます。
どのルーチンも,32ビット・アドレスに代わって 64ビット・アドレスが渡される場所では, プログラミング・エラーに注意する必要があります。 このようなチェックは符号拡張チェックと呼ばれるもので,ビット31 の値に一致した状態で,アドレスの上位32ビットがすべて0,またはすべて 1であることを確認します。このチェックは, この制限を適用しているルーチン・インタフェースで実行できます。
ルーチン・インタフェースを新しく定義するときは, 32ビット・ソース・モジュールから, ルーチンの呼び出しを簡単にプログラムできるように考慮しなければなりません。 また,64ビット・アドレッシングのサポートをもともと意図している言語だけでなく, すべてのOpenVMSプログラミング言語で作成された呼び出しを考慮しなければなりません。 新規ルーチンの 32ビット呼び出し側に対して不慣れなプログラミングを強いることを防ぐために, 64ビット呼び出し側だけでなく 32ビット呼び出し側にも配慮しなければなりません。
データがディスクリプタでルーチンに渡される場合には, ディスクリプタへの参照に対してもこの符号拡張チェックが適用されます。
符号拡張チェックが失敗した場合,呼び出されたルーチンは,エラー状態 SS$_ARG_GTR_32_BITSを返します。
あるいは,呼び出されるルーチンが,エラーがない状態で 64ビットの位置に渡されるデータを受け取ることが求められる場合, 符号拡張チェックが失敗したときは,データを 32ビット・アドレス空間にコピーすることができます。ルーチンがデータをコピーする 32ビット・アドレス空間は,ローカル・ルーチン記憶域(つまり, 現在のスタック)です。ローカル記憶域以外の 32ビット位置にデータがコピーされる場合, メモリ・リークおよびリエントラントについて考慮する必要があります。
新規ルーチンを開発する場合, コードへのポインタおよび新規ルーチンへ渡されるすべてのデータ・ポインタは, できるだけ64ビット・アドレス空間を利用することが望まれます。これは, データがルーチンの場合,またはプログラマやコンパイラ,リンカが通常は 64ビット・アドレス空間に配置しない静的データ と一般に考えられる場合であっても同様です。コードと静的データが 64ビット・アドレス空間の中でサポートされる場合, このルーチンを改めて変更する必要はありません。
既存の大半の32ビット・ルーチンは, 64ビット・ディスクリプタが間違って指定されると, 次の理由でエラー状態SS$_ACCVIOを返します。
32ビット・ディスクリプタに加えて 64ビット・ディスクリプタの処理を目的として変換されている,既存の 32ビット・ルーチンを考えてみます。入力ディスクリプタが 64ビット・ディスクリプタであると判断されると, 64ビット・ディスクリプタが指すデータを,まず 32ビットのメモリ位置にコピーした上で,32ビット・メモリの中で 32ビット・ディスクリプタが作成されます。この新しい 32ビット・ディスクリプタを既存の32ビット・コードに渡すことで, ルーチン内部での変更はこれ以上必要ありません。
これに対してitem_list_3形式の項目リストは3つのロングワードで構成されています。 先頭のロングワードには長さと項目コードのフィールドが含まれ,2番目と 3番目のロングワードには, 通常バッファ・アドレスおよび戻り長アドレス・フィールドが含まれています。
32ビット項目リストが2種類存在するため, 64ビット項目リストも2種類定義されています。新しく加えられたitem_list_64a およびitem_list_64bは,32ビットの項目リストにそれぞれ対応します。 どちらの形式の項目リストも,オフセット0および4の位置に,MBOおよび MBMOフィールドをそれぞれ含んでいます。また,両方の項目リストとも, ワード・サイズの項目コード・フィールド,クォドワード・サイズの長さフィールド, およびクォドワード・サイズのバッファ・アドレス・フィールドをそれぞれ含んでいます。 なお,item_list_64b形式の項目リストは, これとは別にクォドワード・サイズの戻り値長アドレス・フィールドも含んでいます。 戻り値長は64ビットです。
項目リストを受け取るルーチンは,フィールドをテストして,これが 32ビット項目リストと64ビット項目リストのどちらであるかを区別しなければなりません。 64ビット項目リストを受け取った場合,ルーチンはエラーを返します。
既存の大半の32ビット・ルーチンは, 64ビット項目リストが間違って渡されたことを次の理由で判別した場合,エラー状態 SS$_ACCVIOを返します。またはこれをシグナル通知します。
32ビット・ポインタと64ビット・ポインタが混在すると, 64ビット・ポインタが期待されている場所に,呼び出し側が間違って 32ビット・ポインタを参照渡しするなどの,プログラミング・エラーを引き起こします。
プログラマによってロングワードしか割り当てられていないのに, 呼び出されたルーチンが64ビット・ポインタを読み込むと, ルーチンの中で間違ったアドレスが使用されることになります。
また,呼び出されたルーチンが64ビット・ポインタを返すと, プログラマによって割り当てられているロングワードに対して 64ビット・アドレスが書き込まれるため,データの破損が発生します。
ポインタを参照渡しされる既存のルーチンには, 64ビット・サポート用の新規インタフェースが必要です。 旧ルーチン・インタフェースは, 32ビット・メモリ位置の中でポインタをこれまでのように渡し, これに対して新規ルーチン・インタフェースは, 64ビット・メモリ位置でポインタを渡します。同じインタフェースを保持した上で, 64ビット・ポインタを渡すと,既存のプログラムに異常が発生します。
例: SYS$CRETVA_64サービスで使用される戻り仮想アドレスは, 参照渡しでポインタを渡すことのできる例です。P0空間および P1空間の中で作成される仮想アドレスは,64ビットすべてが返されますが, 意味があるのは32ビットだけであることが保証されています。SYS$CRETVA_64は 64ビット空間にアドレス空間を作成し,64ビット・アドレスを返すこともできます。 64ビット・アドレスが返るため,返される値は常に64ビットであることが必要です。
メモリ割り当てルーチンは,可能な場合,値(つまりR0) で割り当てられるデータへのポインタを返します。C割り当てルーチン,malloc, calloc,reallocがこの例です。
メモリ管理ルーチンでないルーチンの新規インタフェースで, 出力引数を定義してアドレスを受け取ることは避けてください。 64ビット・サブシステムがメモリを割り当て, 32ビット呼び出し側に対してポインタを出力引数で戻すと,問題が発生します。 呼び出し側は,64ビット・ポインタをサポートまたは指定できない可能性があります。 あるデータへのポインタを返す代わりに, 呼び出し側はバッファへのポインタを用意して, 呼び出されたルーチンがこのユーザ・バッファにデータをコピーすることが望まれます。
参照渡しされる64ビット・ポインタは,ルーチンへの呼び出しが64ビット言語または 32ビット言語で作成できるような環境で定義する必要があります。 64ビット・ポインタはすべての呼び出し側によって渡される必要があることを, 明確に指示しなければなりません。
既存のルーチンが, 割り当てられているメモリのアドレスをルーチン値として返す場合を考えます。 呼び出し側が64ビットを処理できることがわかっていて, ルーチンが入力パラメータを受け取った場合, 64ビット・アドレスを返すことに何も問題はありません。これ以外の場合は, 引き続き32ビット符号拡張アドレスを返さなければなりません。後者の場合, 64ビットを処理できる呼び出し側が 64ビット・メモリの割り当てを優先するのであれば,既存のバージョンに代わって, ルーチンの新バージョンを提供できます。
例:文字列ディスクリプタを操作するLIBRTL内のルーチンは, 渡されたディスクリプタが新しい64ビット形式であれば,呼び出し側が 64ビットを処理できると判断します。この場合は,文字列データに 64ビット・メモリを割り当てても安全です。これ以外の場合は, 32ビット・アドレス・メモリだけを引き続き使用します。
ポインタが32ビット符号拡張アドレスの制約を受けている場合(たとえば, ポインタが32ビット・ルーチンに渡される場合),ルーチンの入り口で, 64ビット・ポインタについて符号拡張チェックが実行されます。 符号拡張チェックが失敗すると,エラー状態SS$_ARG_GTR_32_BITS が呼び出し側に返されます。または 64ビット・アドレス空間に常駐しているデータが検出されると,これが 32ビット・アドレス空間にコピーされます。
新しい構造は,64ビットの呼び出し側も32ビットの呼び出し側も, 余分なコードを使用しないで済むように定義する必要があります。構造では, 64ビットの呼び出し側を対象とするクォドワード・フィールドと, 32ビットの呼び出し側を対象とする 2つのロングワード・フィールドが互いに重なり合って提供されます。 先頭のロングワードは32ビット・ポインタ・フィールドで,次のロングワードは MBSE (must be sign-extension)フィールドです。32ビットの呼び出し側の大半では, MBSEフィールドは0になります。これは,ポインタが 32ビット・プロセス空間アドレスとなるためです。ここで重要なのは,ポインタを 64ビット値として定義し, 32ビットの呼び出し側に対してクォドワード全体を入力する必要があることを明確にすることです。
次の例では,64ビットと32ビットの両方の呼び出し側が,関数routine を呼び出すときに,block構造にポインタを渡し,同じ関数プロトタイプを使用します (dataは別のモジュールで定義され,その構造は不定であるものとします)。
#pragma required_pointer_size save #pragma required_pointer_size 32 typedef struct block { int blk_l_size; int blk_l_flags; union { #pragma required_pointer_size 64 struct data *blk_pq_pointer; #pragma required_pointer_size 32 struct { struct data *blk_ps_pointer; int blk_l_mbse; } blk_r_long_struct; } blk_r_pointer_union; } BLOCK; #define blk_pq_pointer blk_r_pointer_union.blk_pq_pointer #define blk_r_long_struct blk_r_pointer_union.blk_r_long_struct #define blk_ps_pointer blk_r_long_struct.blk_ps_pointer #define blk_l_mbse blk_r_long_struct.blk_l_mbse /* Routine accepts 64-bit pointer to the "block" structure */ #pragma required_pointer_size 64 int routine(struct block*); #pragma required_pointer_size restore入力引数を指定する既存の32ビット・ルーチンの場合, これがポインタを埋め込む構造のときは,別の方法で既存の 32ビット・インタフェースを保持できます。実行時に, 32ビット形式のデータ構造と区別できる,64ビット形式のデータ構造を開発できます。 32ビット形式の構造だけを受け取る既存のコードは, 64ビット形式の構造が指定されると,自動的に異常終了します。
新しい64ビット構造についての構造定義には, 32ビット形式の構造を含む必要があります。 32ビット形式の構造を含むことによって,呼び出されたルーチンは入力引数を, 64ビット形式の構造へのポインタとして宣言し, いずれの場合でも明確に処理することができます。
言語に対して,型のチェックを行う 2種類の関数プロトタイプを用意しなければなりません。 省略時の設定時の関数プロトタイプは,引数を 32ビット形式の構造へのポインタとして指定します。 64ビット形式の関数プロトタイプは,マニュアルに説明されているように, シンボルを定義することによって選択できます。
64ビット対32ビット・ディスクリプタは,これがどのように行われるかを示す例です。
例: 次の例では,シンボル FOODEF64
の状態が,正しい関数プロトタイプと共に,
64ビット形式の構造を選択します。シンボル FOODEF64
が未定義の場合,古い
32ビット構造が定義され,古い32ビット関数プロトタイプが使用されます。
関数 foo_print
を実現するソース・モジュールはシンボル
FOODEF64
を定義し,
32ビットおよび64ビットの呼び出し側からの呼び出しを処理することができます。
64ビットの呼び出し側はフィールド foo64$l_mbm1
を-1に設定します。
foo_print
はフィールドfoo64$l_mbm1が-1であるかどうかをテストすることによって,
呼び出し側が64ビット形式の構造を使用しているか,または
32ビット形式の構造を使用しているかを判断します。
#pragma required_pointer_size save #pragma required_pointer_size 32 typedef struct foo { short int foo$w_flags; short int foo$w_type; struct data * foo$ps_pointer; } FOO; #ifndef FOODEF64 /* Routine accepts 32-bit pointer to "foo" structure */ int foo_print(struct foo * foo_ptr); #endif #ifdef FOODEF64 typedef struct foo64 { union { struct { short int foo64$w_flags; short int foo64$w_type; int foo64$l_mbmo; #pragma required_pointer_size 64 struct data * foo64$pq_pointer; #pragma required_pointer_size 32 } foo64$r_foo64_struct; FOO foo64$r_foo32; } foo64$r_foo_union; } FOO64; #define foo64$w_flags foo64$r_foo_union.foo64$r_foo64_struct.foo64$w_flags #define foo64$w_type foo64$r_foo_union.foo64$r_foo64_struct.foo64$w_type #define foo64$l_mbmo foo64$r_foo_union.foo64$r_foo64_struct.foo64$l_mbmo #define foo64$pq_pointer foo64$r_foo_union.foo64$r_foo64_struct.foo64$pq_point er#define foo64$r_foo32 foo64$r_foo_union.foo64$r_foo32 /* Routine accepts 64-bit pointer to "foo64" structure */ #pragma required_pointer_size 64 int foo_print(struct foo64 * foo64_ptr); #endif #pragma required_pointer_size restore上の例で,構造
foo
および foo64
が同じソース・モジュールの中で交換して使用される場合,シンボル
FOODEF64を削除できます。この場合,ルーチンfoo_printは次のように定義されます。
int foo_print (void * foo_ptr);FOODEF64シンボルを削除することによって,32ビットの呼び出し側と 64ビットの呼び出し側が同じ関数プロトタイプを使用できます。ただし Cソースのコンパイルの際に,厳密な型のチェックが行われることはありません。
APIがOpenVMS VAX上で利用できない場合であっても,すべての API上での一貫性を実現するため,このガイドラインに従ってください。
ページレットは不便な単位です。これはVAXとの互換性を目的として開発され, OpenVMS Alpha上で,OpenVMS VAX互換インタフェースの中で使用されています。 ページレットはそのサイズがVAXページに等しく, ページ・サイズに依存しない単位とは考えられません。これはAlpha上の CPU固有ページとしばしば混乱されるためです。
例: EXPREG_64内のLength_64引数は,クォドワード・バイト・カウントとして, 値で渡されます。
参照渡しのデータが自然にアラインされていないために呼び出されたルーチンが正しく実行できない場合, ルーチンは明示的にチェックを実行し, アラインされていない場合はエラーを返す必要があります。たとえば, ロック付きロード,条件付き保存がルーチンの内部でデータについて実行される場合, データがアラインされていないと,ロック付きロード, 条件付き保存は正しく処理されません。
例: 32ビットAPIが情報をディスクリプタで渡していた場合,新規イ ンタフェースは同じ情報をディスクリプタで渡さなければなりません。
例: SYS$EXPREG_64はP0,P1,およびP2プロセス空間で動作します。 SYS$EXPREG_64は$EXPREGの機能スーパセットであるため, 呼び出し側はすべての呼び出しをSYS$EXPREGに置換できます。
この規則は,ほかのルーチンに対しても同じように推奨されます。
例:
古いシステム・サービスSYS$CRETVAのC関数宣言は,次の形式で行われます。
#pragma required_pointer_size save #pragma required_pointer_size 32 int sys$cretva ( struct _va_range * inadr, struct _va_range * retadr, unsigned int acmode); #pragma required_pointer_size restore新しいシステム・サービスSYS$CREATE_VAのC関数宣言は,次の形式で行われます。
#pragma required_pointer_size save #pragma required_pointer_size 64 int sys$cretva_64 ( struct _generic_64 * region_id_64, void * start_va_64, unsigned __int64 length_64, unsigned int acmode, void ** return_va_64, unsigned __int64 * return_length_64); #pragma required_pointer_size restoreSYS$CRETVA_64の新規ルーチン・インタフェースは,
_va_range
構造の中で埋め込みポインタを訂正し, 64ビットの region_id_64
引数を参照で渡し,
64ビットの length_64
引数を値で渡します。