[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


VMS上でのGCCの使用

以下に、 VMS上でのGCCの使い方を示します。

VMSにおけるインクルード・ファイル

UNIXとVMSのファイルシステムの相違のために、 GCCは、 `#include'されるファイル名をVMSが理解できる名前に変換します。 基本的な方針は、 インクルード・ファイルが指定されている箇所に接頭辞を付けて、 ファイル名全体をVMSファイル名に変換してからそのファイルをオープンしようとする、 というものです。 GCCは、 ファイルのオープンが成功するまで、 複数の接頭辞を1つずつ順番に試みます。

  1. 最初に試みられる接頭辞は、 論理名`GNU_CC_INCLUDE:'です。 これは、 GNU Cのヘッダ・ファイルが昔から格納されている場所です。 標準的ではない場所にヘッダ・ファイルを置きたい場合は、 論理名`GNU_CC_INCLUDE'にサーチ・リストを割り当てることもできます。 このサーチ・リストの個々の要素には、 ルートからの論理名と組み合わせて使用するのに適切な名前を指定します。
  2. 次に試みられる接頭辞が`SYS$SYSROOT:[SYSLIB.]'です。 これは、 VAX-Cのヘッダ・ファイルが昔から格納されている場所です。
  3. インクルード・ファイルの指定が、 そのままで正当なVMSファイル名であれば、 インクルード・ファイルのオープンを試みる際に、 プリプロセッサは、 接頭辞を付けずにその名前を使用します。
  4. ファイルの指定が正当なVMSファイル名でない場合 (例えば、 デバイス指定子やディレクトリ指定子が含まれない場合や、 文字`/'が含まれる場合)、 プリプロセッサはそのファイル名をUNIX記法からVMS記法に変換しようと試みます。 この変換は、 以下のように実現されます。 まず、 最初のディレクトリ名をデバイスとします。 次に、 残りのディレクトリをVMSフォーマットのディレクトリ名に変換します。 例えば、 `X11/foobar.h'という名前は、 `X11:[000000]foobar.h'`X11:foobar.h'のうち、 実際にオープンすることができる名前に変換されます。 このような方針により、 論理名がヘッダ・ファイルの実際の存在場所を指すようにすることが可能になります。
  5. これらの方針のいずれも成功しなかった場合、 `#include'は失敗に終わります。
#include foobar

という形式のインクルード指示子は、 VAX-CとGCCの間で非互換性の問題をよく引き起こします。 VAX-Cはこれを、 標準的な#include <foobar.h>指示子のように取り扱います。 これは、 GCCにより実装されているANSI Cの振る舞いとは互換性がありません。 GCCでは、 名前foobarはマクロとして展開されます。 マクロ展開によって、 #includeの2つの標準的な形式のいずれかが、 最終的には生成されなければなりません。

#include "file"
#include <file>

この非互換性の問題に直面した場合、 最も良い解決策は、 ソース・コードを修正して、 2つの標準的な形式のいずれかに#include指示子を書き直すことです。 こうすれば、 どちらのコンパイラを使っていても問題なく動作します。 ちゃんとしていなくてもよいから手っ取り早く直してしまいたい場合は、 そのファイル名を、 適切に展開されるマクロとして定義してしまいます。 例えば、 以下のようにします。

#define stdio <stdio.h>

この名前が、 プログラム中で別の名前と衝突を起こさない限り、 これで問題なく動作するでしょう。

VAX-Cが、

#include "foobar"

はファイル`foobar.h'をインクルードするよう要求しているものと想定するということが、 非互換性のもう1つの原因になっています。 GCCはこのような想定を行いません。 代わりに、 要求を字義どおりに受け取って、 `foobar'というファイルを読み込もうとします。 この問題を回避するのに最も良い方法は、 インクルード指示子の中で、 望ましいファイル拡張子を常に明示的に指定することです。

VMS用のGCCは、 一群のインクルード・ファイルとともに配布されています。 これらのインクルード・ファイルがあれば、 ほとんどの一般的な用途のプログラムをコンパイルするのには十分です。 GCCのディストリビューションの中には、 VMSシステムに固有の関数群のための定数や構造体を定義するヘッダ・ファイルは含まれていませんが、 これらの関数をGCCで使うことができない理由はありません。 これを行うには、 まず最初にヘッダ・ファイルを作らなければならないかもしれません。 パブリック・ドメインのユーティリティUNSDL (これはDECUSテープの中に含まれています) を使って生成するか、 システム・マクロ・ライブラリから関連するモジュールを取り出して、 エディタを使ってCのヘッダ・ファイルを作成します。

#includeでインクルードされるファイル名に、 DECNETノード名を含めることはできません。 明示的にであれ、 論理名を介して暗黙的にであれ、 ノード名を使おうと試みると、 プリプロセッサはI/Oエラーを報告してきます。

VMSおける広域宣言

GCCは、 VAX-Cのキーワードであるglobalrefglobaldefglobalvalueを提供していません。 GNUアセンブラGASのあまり知られていない機能を使って、 これと同等の効果を実現することができます (そのためには、 バージョン1.39以降のGASが必要になります)。 以下のマクロを使えば、 かなり自然な形でこの機能を使うことができるようになります。

#ifdef __GNUC__
#define GLOBALREF(TYPE,NAME)                      \
  TYPE NAME                                       \
  asm ("_$$PsectAttributes_GLOBALSYMBOL$$" #NAME)
#define GLOBALDEF(TYPE,NAME,VALUE)                \
  TYPE NAME                                       \
  asm ("_$$PsectAttributes_GLOBALSYMBOL$$" #NAME) \
    = VALUE
#define GLOBALVALUEREF(TYPE,NAME)                 \
  const TYPE NAME[1]                              \
  asm ("_$$PsectAttributes_GLOBALVALUE$$" #NAME)
#define GLOBALVALUEDEF(TYPE,NAME,VALUE)           \
  const TYPE NAME[1]                              \
  asm ("_$$PsectAttributes_GLOBALVALUE$$" #NAME)  \
    = {VALUE}
#else
#define GLOBALREF(TYPE,NAME) \
  globalref TYPE NAME
#define GLOBALDEF(TYPE,NAME,VALUE) \
  globaldef TYPE NAME = VALUE
#define GLOBALVALUEDEF(TYPE,NAME,VALUE) \
  globalvalue TYPE NAME = VALUE
#define GLOBALVALUEREF(TYPE,NAME) \
  globalvalue TYPE NAME
#endif

(名前の先頭にある接頭辞_$$PsectAttributes_GLOBALSYMBOLは、 アセンブラが、 シンボルの属性を変更した後に取り除きます) これらのマクロは、 VMSバイナリ・ディストリビューションの中のヘッダ・ファイル`GNU_HACKS.H'において提供されています。 使用例を以下に示します。

GLOBALREF (int, ijk);
GLOBALDEF (int, jkl, 0);

マクロGLOBALREFとマクロGLOBALDEFは、 そのまま単純に配列に対して使用することはできません。 というのは、 配列の次元を宣言の右側に挿入することができないからです。 しかし、 以下のように、 最初に配列型に対してtypedefを定義しておけば、 これらのマクロを使用して配列を宣言することができます。

typedef int intvector[10];
GLOBALREF (intvector, foo);

配列の初期化子や構造体の初期化子を使用した場合も、 このマクロはうまく機能しなくなります。 この場合には、 初期化子をまた別のマクロとして定義することも可能ですし、 GLOBALDEFを手作業で展開することも可能です。 GLOBALDEFマクロを大規模な配列に対して使用したいが、 配列の個々の要素を明示的に初期化するのは避けたいという場合もあるでしょう。 この場合は、 {0,}のような初期化子を使用することができます。 これは、 配列全体を0で初期化します。

この実装の欠点は、 GLOBALVALUEREFGLOBALVALUEDEFを使用して宣言された変数が、 常に配列になるということです。 例えば、 宣言

GLOBALVALUEREF(int, ijk);

は、 変数ijkint [1]型の配列として宣言します。 これは、 globalvalueが実際には定数であり、 リンカが通常アドレスとみなすものが、 その「値」となるからです。 Cにおいては、 整数値はこのようには扱われません。 このように扱われるのは、 配列ですから、 シンボルを配列名として扱うことで、 一貫性のある結果が得られます。 その値が正しくない型を持つように見えるということだけが欠点です。 この配列の要素にアクセスしようなどと試みてはいけません。 この配列は要素をまったく持っていません。 配列の「アドレス」は、 実際の記憶域のアドレスとは異なる可能性があります。

シンボルが実際には配列であるという事実のために、 その変数が使用されているところで、 警告の出力されることがあるかもしれません。 こうした警告を回避するには、 型のキャストを挿入してください。 以下に例を示します。 この例では、 マクロの展開結果がそのマクロ自身と同じ名前を使うことができるという、 ANSI Cの特徴を利用しています。

GLOBALVALUEREF (int, ss$_normal);
GLOBALVALUEDEF (int, xyzzy,123);
#ifdef __GNUC__
#define ss$_normal ((int) ss$_normal)
#define xyzzy ((int) xyzzy)
#endif

globaldefglobalrefを、 列挙型の変数に対して使用してはなりません。 列挙型のサポートは、 まだ実装されていません。 代替手段として、 変数を整数型にして、 個々の列挙値(enumeration value)に対してglobalvaluedefを使用してください。 この実例は、 以下のようになるでしょう。

#ifdef __GNUC__
GLOBALDEF (int, color, 0);
GLOBALVALUEDEF (int, RED, 0);
GLOBALVALUEDEF (int, BLUE, 1);
GLOBALVALUEDEF (int, GREEN, 3);
#else
enum globaldef color {RED, BLUE, GREEN = 3};
#endif

VMSに関するその他の事項

main関数の中で戻り値が明示的に指定されていない場合、 GCCはデフォルトで1を返すよう自動的に調整します。 VMSはこれを、 通常の正常終了を示すステータス・コードとして解釈します。 バージョン1のGCCは、 このデフォルト値を提供していませんでした。

VMS上のGCCは、 GNUアセンブラGASとの組み合わせにおいてのみ正しく機能します。 VMSデバッガ用の正当なデバッグ情報を生成するためには、 バージョン1.37以降のGASが必要です。 GASにより生成されたオブジェクト・ファイルは、 通常のVMSリンカを使ってリンクします。

以前のバージョンのGCCでは、 生成されたコードが、 共用ライブラリ`VAXCRTL'とリンクされた場合に、 たまに奇妙な結果をもたらすことがありました。 現在のバージョンでは、 問題なく動作するはずです。

const指定された広域変数を使用する場合には、 1つ注意しなければならないことがあります。 その変数を使用するすべてのソース・ファイルの中の、 その変数のすべての外部宣言において、 const修飾子が指定されなければならないということです。 こうしないと、 その変数の属性が矛盾していることに関する警告をリンカが出力します。 このような警告が出力されてもプログラムは動作するでしょうが、 その変数は書き込み可能な記憶域に置かれるでしょう。

VMSリンカが広域シンボルの大文字・小文字を区別するにもかかわらず、 ほとんどのVMSコンパイラは広域シンボルの名前を大文字に変換しますし、 ほとんどのランタイム・ライブラリも大文字のシンボル名を持っています。 こうしたルーチンを確実に呼び出すことができるように、 GCCは (アセンブラGASによって) 他のVMSコンパイラと同様に、 広域シンボル名を大文字に変換します。 しかし、 Cにおける通常の慣習では大文字・小文字は区別されるので、 GCCは (GASを通じて) すべての文字が小文字から構成されるシンボルを除いて、 すべてのシンボル名を拡張することによって、 通常のCの振る舞いを維持しようと試みます。 具体的には、 シンボル名を最大23文字までに切り詰めてから、 これら23文字の大文字・小文字のパターンをエンコードしたものを、 切り詰められたシンボル名の末尾に付加します。 ドル記号を1つでも含むシンボル名は例外で、 これらは拡張されることなく、 直接大文字に変換されます。

名前の拡張は、 別のコンパイラにより生成されたコンパイル済みのライブラリ (例えばXlib) を使うプログラムに対して良くない結果をもたらします。 コンパイラ・オプション`/NOCASE_HACK'を使うことによって、 名前の拡張を禁止することができます。 これは、 通常VMS上でそうであるように、 Cの外部関数や外部変数を大文字・小文字に依存しないようにします。 別の手段として、 そのようなライブラリ内の関数や変数を参照しているすべての部分を小文字を使って書くこともできます。 ただし、 VMS上ではこれで問題なく動作するでしょうが、 他のシステムに対する移植性はありません。 コンパイラ・オプション`/NAMES'によっても、 広域における名前の取り扱いに関する制御を行うことができます。

GNU C++では、 関数名や変数名はまた異なった取り扱いを受けます。 GNU C++コンパイラは、 関数名に対して名前のマングル処理(name mangling)を実行します。 これは、 関数の取る引数のデータ型に関する情報が関数名に付加されることを意味します。 その1つの結果として、 関数の名前が非常に長くなる可能性があります。 VMSのリンカは名前の最初の31文字しか認識しないので、 個々の関数や変数が、 31文字で表現可能なユニークな名前を確実に持つようにするために、 特別の処理が行われます。

名前 (必要な場合には名前の拡張部分も含めた) 長さが32文字未満の場合には、 特別な処理は実行されません。 名前の長さが31文字を超える場合には、 アセンブラ(GAS)は、 関数名をもとにハッシュ文字列(hash string)を生成し、 関数名を23文字に切り詰めてから、 切り詰められた名前の末尾にハッシュ文字列を付加します。 コンパイラ・オプション`/VERBOSE'が使用されると、 アセンブラは、 切り詰められた個々のシンボルの、 完全な名前と切り詰められた名前を表示します。

libg++を使用するプログラムをコンパイルするときには、 `/NOCASE_HACK'コンパイラ・オプションを使うべきではありません。 libg++には、 大文字・小文字を区別しない環境では互いに区別することができなくなってしまうような オブジェクトのインスタンス (例えば、 Filebuffilebuf) がいくつか存在します。 そのために、 名前の拡張処理を個別に(選択して)禁止する必要のある場合がでてきます (例えば、 libg++とXlibを同一プログラムの中で併用する場合)。 これを行うために用意された特別な機能はありませんが、 大文字・小文字の混在するシンボルのうち、 名前の拡張処理を禁止したいものに対して個別にマクロを定義することによって、 同じことを実現することができます。 このマクロは、 シンボル名を構成するすべての文字を小文字に換えたものに展開されなければなりません。 以下に例を示します。

#define StuDlyCapS studlycaps

これらのマクロ定義は、 ソース・コードに対する変更を最小にするために、 ヘッダ・ファイルの中に置くことができます。


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]