不完全型と先行宣言

こんにちは、めのんです!

今回解説するのは不完全型とそれを利用した先行宣言についてです。
前回予告したとおりです。

Cの型は大きく分けると、「オブジェクト型」、「関数型」、そして「不完全型」があります。
今回はその3つめ、不完全型を解説することになります。

不完全型はどんな型?

「不完全型(incomplete type)」というのはJIS X3010:2003では次のように定義されています。・

オブジェクトを規定する型で,その大きさを確定するのに必要な情報が欠けたもの

この定義には2つのポイントがあります。

  • オブジェクトを規定する型
  • 大きさを確定するのに必要な情報が欠けている

それぞれのポイントについて順に見ていきますね。

オブジェクトを規定する型

まず、不完全型というのはあくまでもオブジェクトを規定するための型だということです。
ですので、不完全型へのポインタとオブジェクト型へのポインタは、境界調整の問題などはありますが、一応相互に変換することができます。

ところが、関数型はまったく異質なものなので、不完全型へのポインタと関数型へのポインタは相互変換できません(強引に変換しても意味のある結果を得られません)。

ここはすごく大事なポイントなんですが、実際のプログラムではこの部分を無視しているコードをときどき見かけます。
場合によってはそれでも動くのですが、決して褒められたものではないですね。

大きさを確定するのに必要な情報が欠けている

次に、オブジェクト型の場合はメモリ上の何バイトを占有するのかがハッキリしていますが、不完全型はサイズがハッキリしない型ということになります。

といっても、どこかではサイズを決定しないといけません。
不完全型はあくまでも暫定的な宣言を行うための型で、最終的にはどこかでその不完全型と互換性のあるオブジェクト型として定義されていることがほとんどです。

不完全型の種類

不完全型には大きく分けて次の3種類があります。

  • void型
  • 要素数未定の配列型
  • タグのみ宣言した構造体型または共用体型

それでは、これら3つについて順に解説していきますね。

void型

void型についてはこれまで何度も登場しました。
用途としては3つで、関数の宣言で仮引数や返却値が無いことを表すのと、voidへのポインタです。

関数の仮引数や返却値はともかく、voidへのポインタはオブジェクト型へのポインタと相互変換できますね。
でも、ポインタの参照先はvoidなのでサイズがいくつなのかは確定しません。
典型的な不完全型だといえます。

要素数未定の配列型

配列型を宣言するときは、たとえば次のように要素数を指定するのが普通です。

int array[10];

ところが、配列の各要素にアクセスする場合も、配列の先頭要素へのポインタが必要になる場合も、要素数がわからなくてもとくに問題にはなりません。
Cの添字演算子は添数が要素数の範囲に収まっているかどうかはチェックしてくれませんので、要素数はとくに必要ないんです。

だったら、とりあえず

int array[];

とだけ宣言しておいて、どこか別の場所でちゃんと要素数を確定させることにしても問題ありません。

関数の引数に配列を指定する場合でも、実際にはポインタ型になってしまいますから、次のように書いてもいいのと同じことだといえるでしょう。

void func(int array[]);

タグのみ宣言した構造体型または共用体型

構造体や共用体は「struct タグ」や「union タグ」が型名になります。
メンバがどうなっているのかわからなくても、とにかくタグ名とstructなのかunionなのかだけ特定しておけば型としては成立します。
それらも不完全型の一種になります。

関数の引数として渡す場合など、メンバの構成や全体のサイズがわからなくても、struct タグやunion タグだけでもそのポインタを作るだけなら問題ありません。

struct sample *p;

とりあえず上のように書けば、それがstuct sample型のポインタとして宣言できてしまいます。

もちろんこのようなタグだけの不完全型では配列は作れませんし、(ポインタではなく)構造体型や共用体型そのもののオブジェクトを定義することもできません。

それでも、タグしか無いこうした不完全型にはいろんな用途があります。
具体的な使い方については、次回以降解説できればと考えています。

ところで、タグを使う型にはもうひとつ列挙体がありましたね。
残念ながら(残念でもないかな?)列挙体のタグだけの不完全型というのはCには存在しません。
ここは間違いやすいところなのでご注意ください。

ところで、タグだけで、

struct A;
union B;

のように型名を宣言することを「先行宣言」と呼ぶ場合があります。
関数原型と似たような目的で使いますが、もちろん別の概念です。

「先行宣言」というのは規格の用語ではありませんが、よく使う表現なので覚えておくといいでしょう。


今回の解説は以上となります。
かなり地味な内容でしたが大事な概念ですので覚えておいてくださいね。

次回は、せっかくなので構造体の先行宣言を使った実例を解説してみたいと思います。
どうぞご期待ください。