-

Specific Features

提供: HI-TECH C for CP/M Fan WIKI(JP)
移動先: 案内検索

機能特徴

HI-TECH Cコンパイラは、他のCコンパイラとほぼ互換の多くの特徴を持ち、より信頼できるプログラミング手段に貢献します。


ANSI C標準の互換性

これを記述している時点では、C言語のANSI規格のドラフトがとても進んだステージにありましたが、いまだ公式な規格ではありませんでした。そのため、規格の要求に応じることはできませんでしたが、ANSI規格のドラフトにおける新しい、変化した特徴の大部分を取り込んでいます。そのため、ある意味では多くの人はこれを"ANSI準拠"とみなすことでしょう。

型のチェック

以前のCコンパイラは型のチェックに緩いアプローチを採用していました。これはUNIX Cコンパイラに顕著で、ほとんどの任意の型の混在をexpression内で許可します。HI-TECH Cコンパイラはより厳格な型のチェックを行いますが、もし、そのエラーが害のないものと、ユーザーがわかった場合、ほとんどの場合はコンパイルの続行を許可しつつ警告メッセージを出します。これが起こるのは、例えば、INT型の数値がポインタ変数に割り当てられた場合です。生成されたコードはほとんどユーザーの意図したものでしょうが、実際には、ソースコードでエラーがあり、ユーザーはすぐに必要な場所をチェックして修正します。

メンバ名

初期のCコンパイラでは、例外的な状況以外は、異なる構造において区別されたメンバ名が要求されていました。HI-TECH Cはほとんどの最近のCの実装と同じく、異なる構造体や共用体において重複を許可します。メンバが定義されている構造体の型の、式(expression)のコンテクストにおいてのみメンバ名が認識されます??。実用的に言えば、これはメンバ名が"."もしくは"-"演算子の右項でのみ認識されるということを意味し、式の左項はもしくはメンバが定義されているのと同じ構造体、もしくは構造体へのポインタと同じです??。これは一つ以上の構造体で衝突を起こさずに構造体名の再利用を許可するだけでなく、メンバ使用の型チェックを許可します。他のCコンパイラとの共通のエラーは、違う型の構造体へのポインタを伴うメンバの使用、もしくはこれよりよりひどい単純な型へのポインタである変数です???


しかしながら、このように定義されない構造体のポインタや何かとして、ユーザーが使いたい場合にはここから外れるものもあります。これは型変換を使用するものです。例えば、いくつかのレジスタからなるメモリマップトI/Oデバイスにアクセスしたいと仮定します。定義は以下の通りです。


   struct io_dev
   {
           short   io_status;    /* status */
           char    io_rxdata;    /* rx data */
           char    io_txdata;    /* tx data */
   };

   #define RXRDY   01           /* rx ready */
   #define TXRDY   02           /* tx ready */

   /* デバイスの(絶対)アドレスの定義 */
   /* define the (absolute) device address */

   #define DEVICE  ((struct io_dev *)0xFF00)

   send_byte(c)
   char c;
   {
           /* wait till transmitter ready */
           while(!(DEVICE->io_status & TXRDY))
                   continue;
           /* send the data byte */
           DEVICE->io_txdata = c;
   }

図2.絶対アドレスでの型変換

この例では、問題となるデバイスは16bitのステータスポートで二つの8bitデータポートがあります。デバイスのアドレス(つまりステータスポートのアドレス)は(16進数の)-FF00として与えられます。このアドレスは必要な構造体ポインタへと型変換され構造体のメンバ名が使用可能になります。これで生成されるコードは要求通りに絶対メモリにアクセスできるものです。

右項とメンバ名のいくつかの例については図3て提示されています。

符号なしの型

HI-TECH Cはすべての整数型、つまりCHAR,SHORT,INT,LONG、に対して符号なしの変種を持ちます。 符号なしの値が右シフトした場合、論理シフトの役割を果たします。つまり、最も右のビットに入れるということです。同様に符号ありの値が右シフトした場合も最も右のビットに符号を追加します??

算術演算子

intよりも短い場合には算術演算子がより効果的に働く機器において、intよりも短いオペランドは必要がない場合にはint以上には拡大されません。

例えば、二つの文字が他の文字に保存される場合


       struct fred
       {
             char      a;
             int       b;
       }     s1, * s2;

       struct bill
       {
               float   c;
               long    b;
       }       x1, * x2;

       main()
       {

               /* 誤り - cはfredのメンバではありません */
               s1.c = 2;

               /* 正しい */
               s1.a = 2;

               /* 誤り - s2 はポインタです */
               s2.a = 2;

               /* 正しい */
               x2->b = 24L;

               /* 正しいのですが、longからintの型変換に注意してください */
               s2->b = x2->b;
       }


図3. メンバの用法の例

上記の例は8bitで算術的に計算されるので、8bitの先頭のオーバーフローは失われます。しかしながら、もし、二つの文字がintに保存される場合、正しい結果を保証するため、加算は16bitで行われます。

ANSIスタンダードのドラフトによれば、doubleよりもむしろfloatによる計算の処理の方が、double精度に変換して再び戻すよりも精度が短いです。


構造体の扱い

HI-TECH Cは構造体の割り当て、構造体引数、構造体相当の?関数を完全に普遍的に実装しています。図4の例は構造体を返す関数の例です。いくつかの適切な(日適切な)使用法も同じく提示しています。

struct bill
{
char    a;
int     b;
}
afunc()
{
struct bill     x;

return x;
}

main()
{
struct bill     a;

a = afunc();            /* ok */
pf("%d", afunc().a);    /* ok */
/* 不適切な例:afunc()にはアサインできないのでafunc().aも同様?? */
afunc().a = 1;

/* 同じ理由で不適切です */
afunc().a++;
}

図4.構造体を返す関数の例

列挙型

HI-TECH Cは列挙型をサポートしています。これらは名前のついた定数の構造的な定義方法を提供します。

列挙型の使用はUnixのCコンパイラで許されている以上に制限されていますが、LINTで許可されているよりは柔軟性があります。特に列挙型の式(expression)は、配列インデックスとして、もしくはSwitch文(statement)のオペランドとして多次元配列に対しても使用できます。 算術演算も列挙型に対して実行でき、列挙型の式(expression)も代入?関係演算子ともに比較可能です。列挙型の使用例については図5で提示しています。


初期化のシンタックス

カーニハンとリッチーによる「プログラミング言語C」ではペアの括弧が、あるコンテクストにおけるイニシャライザでは省略できる、と言っています。一方ANSIドラフトではCプログラムに従うにはどのイニシャライザにおいてもすべての括弧を含まなければいけないか、全部を残しておく必要があるとしています??。HI-TECH Cはコンパイラのフロントエンドが、イニシャライズされる配列のサイズを決定し、またどの括弧が省略されたかについてあいまいさがないために ペアの括弧の省略を許可しています??


/* a represents 0, b -> 1 */
enum fred { a, b, c = 4 };

main()
{
enum fred       x, y, z;

x = z;
if(x < z)
func();
x = (enum fred)3;
switch(z) {
case a:
case b:
default:
}
}

図5.列挙型の使用

曖昧さを回避するために、カッコのペアが存在するとき、どの括弧のペアでも、これらの閉じ括弧も存在しなければなりません。曖昧さが存在する場合、コンパイラは("initialization syntax")の文句をいうでしょう。


関数プロトタイプ

ANSI Cに取り込まれた新しい特徴は"関数プロトタイプ"として知られており、Cに引数チェックの機能を提供しています。つまり、コンパイラに対してコンパイル時に実際に与えられた引数が、関数の期待するきちんとしたパラメータなのかをチェックします??この機能は関数定義の中において(外部宣言でも実際の定義においても)その関数に対するパラメータの型をインクルードすることをプログラマに許容します。例としてこのコードフラグメントが図6において二つの関数プロトタイプを提示します。

void fred(int, long, char *);

char *
bill(int a, short b, ...)
{
return a;
}

図6.関数プロトタイプ

最初のプロトタイプは関数fred()の外部宣言で、一つのint型引数、一つのlong型引数、charポインタ型の引数を取ります。fred()をどのように使っても、スコープ内のプロトタイプ宣言は実際のパラメータに対し、プロトタイプに対する数値、型をチェックさせます。つまり、二つの引数のみが与えられたり、int型の値が三番目の引数で与えられた場合には、コンパイラがエラーをレポートします。

二番目の例では、関数bill()が二つ、もしくはそれ以上の引数を期待しています。最初と二番目はintとshortにそれぞれ変換されますが、残りのものは(もし存在するなら)どんな型でもかまいません。省略のシンボル(...)は、他の引数に続いて、ゼロ、もしくはどんな型の引数でもその後に続く、ということをコンパイラに示しています。省略シンボルは引数リストでなくてはならず、プロトタイプのただ一つの引数であってはいけません。 関数への全てのプロトタイプはきちんと対応していなくてはいけませんが、括弧内のパラメータ名だけを付けるという、古いスタイルの関数の定義においては有効です。プロトタイプ宣言に従うことで、引数の数値と型の対応が提供されるのです。この場合、関数定義はプロトタイプ宣言のスコープ内であることが基本です。

特定されていない引数へのアクセス(つまりプロトタイプに表れる省略の部分で提供される引数)はヘッダファイル<stdarg.h>内でマクロ定義されなければいけません。va_start,va_arg,va_endのマクロはこれを定義しています。詳細はライブラリ関数一覧のva_startを参照してください。

プロトタイプがスコープに入っていないプロトタイプと関連づける関数の使用というのは、とても重たいエラーであり、プロトタイプは「必ず」関数が呼ばれる前に(可能であればヘッダファイルで)宣言されなければいけません。このルールを満たせないことはプログラムの奇妙な動作を引き起こします。HI-TECH Cは("func() declared implicit int")という警告メッセージを、正確な定義なしに関数が呼び出された時はいつでも発行します。全ての関数とグローバル変数を一つ、もしくはそれ以上の数のヘッダファイルで、関数が定義、もしくは参照されている場所のどの場所でも定義するのはよいやり方です。

VOIDとVOIDへのポインタ

VOID型は関数が値を返さないということをコンパイラに伝えるために使われます。VOID関数からの返り値はエラーとしてフラグが立ちます??

VOID*型、つまりVOIDへのポインタは"ユニバーサルな??"ポインタ型として使用されます。これは一般目的ストレージアロケータや他のポインタ型の違う値をアサインし得る??ものを返すポインタのような用途をアシストします???コンパイラは型変換やVOID*方と他のポインタ型の相互の型変換することなく許可します??プログラマはこの機能を注意深く、またどのようなVOID*の値も他のポインタ型に使用可能であることに注意してください。つまり、そのようなポインタとの連携はどんなオブジェクトでも保存するのに適しています??


型修飾子

ANSI Cスタンダードはtype qualifiers「型修飾子」という概念を紹介しています。これらは適用される型を修飾するキーワードです。ANSI Cで定義されている型修飾子はconstとvolatileです。HI-TECH Cでは他の修飾子のいくつかを実装しています。追加の修飾子は以下の通りです。

  • far
  • near
  • interrupt
  • fast interrupt
  • port

全てのバージョンのコンパイラが追加の型修飾子をすべて実装している訳ではありません。詳細な情報は機種依存のセクションを参照してください。

型修飾子を使って定義を構築する場合、意味論的に正確な定義でよく混乱しがちです。いくつかのよくやる方法はこれを簡単にします。まず型修飾子が定義の左側に現れ、ストレージクラス修飾子と基本的な型が任意の順序に現れる、などです。

static void interrupt   func();

上記の例は次の例と意味的に同じです。

interrupt static void   func();

修飾子がこのコンテキストで現れる場合、定義の基本的な型が現れます。一つ、もしくはそれ以上の'*'(star)ポインタ修飾子の右に修飾子が現れる場合、右から左へ定義を読む必要があります。

char * far fred;

つまり上記の例は"fredがcharに対するfarポインタ"と読めます。これが意味するのは、fredがfarによって修飾されており、それがさすのはcharではない、ということです。

char far * bill;

他方ではこのように"billはfar charへのポインタ"で、billが示すcharがfarアドレス空間に置かれている、と読めます??。8086コンパイラのコンテキストでは、billは32bitポインタですが、fredは16bitポインタです。billが"farポインタ"として参照しているように見えますが、語句の用法上では"farへのポインタ"が優先されます??

5.12.

Cプログラムにインラインアセンブラを提供する方法は二つあります。一つ目はプログラム内のどこでも数行のアセンブラが許可されています。これは#asmから#endasmプリプロセッサディレクティブを介するものです。これらの二つのディレクトリに挟まれたいかなる行も、コンパイラによってそのまま直にアセンブラファイルにコピーされます。あるいCステートメントのどこかで、asm(string)構造体を使うことができます。stringはアセンブラファイルにコピーされます。コンパイラ生成コードに影響するため、インラインアセンブラコードには十分注意する必要があります。

PRAGMAディレクティブ

ANSIスタンダードドラフトはコンパイラに対して、コンパイラプロセスのいろいろなことをコントロールする、#pragmaプリプロセッサディレクティブを許可しています。HI-TECH Cはいまのところただ一つ、packディレクティブのみpragmaをサポートしています。 これはメンバが構造体に配置されるやりかたのコントロールを許可します。デフォルトではいくつかのコンパイラ(特に8086や68000コンパイラ)はマシンアクセスの最適化の境界でも構造体メンバを配置します????packプラグ間はパッキング要素の最大値を指定します。例えば#pragma pack1(1)はコンパイラに対し、構造体メンバの間に追加のパディングを行わない、つまりすべてのメンバが境界で1によって分けられる??ということを意味します。同様に#pragma pack(2)は境界が2で分けられて配置されることを許可します。packプラグマでケースを使用しない場合はそのデータタイプに対して使われる配置以上が強制されます??

一つ以上のpackプラグマがプログラム内で使用可能です。いつ使用しても他のpackプラグマによって変更されるか、ファイルの終了まで、強制は続きます。packプラグマを<stdio.h>のようなファイルの前に使用しないでください。ランタイムライブラリのデータ構造に誤った定義を引き起こします。