C
C言語Day 16

Ancient C探訪記:構造体編

おことわり: この記事では「1975年頃のC言語」仕様を解説します。2017年現在のC言語仕様とは異なるため、あなたのC言語ライフには役立たないことを予めご承知おきください。

(本投稿は Ancient C探訪記 シリーズの一部です。)

つづいては、実用的なプログラムには不可欠なデータ構造「構造体(structure)」を見ていきます。

※※※ この記事にはショッキングな内容が含まれる可能性があります ※※※

Ancient Cの構造体

構造体の定義

"C Reference Manual" §8.2 Type specifiers より部分引用します。Ancient Cの構造体定義は、標準C(ANSI C、C90)のそれと大きな差異はありません。

struct { type-decl-list }
struct identifier { type-decl-list }
struct identifier

なお、identifier は構造体を区別するタグ名(識別子)です。type-decl-list は構造体メンバの宣言リストが並びます。

メンバ名に関する注意

標準Cとの大きな違いは、構造体における “名前” の扱い方にあります。§8.5 Structure declarations より言及箇所を引用します:

The names of structure members and structure tags may be the same as ordinary variables, since a distinction can be made by context. However, names of tags and members must be distinct. The same member name can appear in different structures only if the two members are of the same type and if their origin with respect to their structure is the same; thus separate structures can share a common initial segment.
(参考訳) コンテキストによって区別可能なため、構造体メンバや構造体タグの名前は通常の変数と同一でもよい。しかしながら、各タグや各メンバの名前は互いに異ならなければならない。同じメンバ名が異なる構造体に存在してもよいが、2つのメンバが同じ型を持ち、かつそれぞれの構造体中での基点が等しいときに限る; つまり異なる構造体は共通初期セグメントを共有可能である。

これは、一部の例外を除いて 異なる構造体であってもメンバ名の衝突は許されない という仕様です。下記コードのように異なる構造体(struct user, struct id)に存在する同名メンバ id は、メンバ名の衝突により不正なプログラムとなります。1

ancient-conflict.c
struct user {
  int id;  /* NG */
  char name[20];
};

struct item {
  int price;
  int id;  /* NG */
};

例外的な条件として、共通初期セグメント(common initial segment)をもつ構造体同士であれば、同型・同名メンバの存在が許されます。この振る舞いは標準Cにおける共用体に通じるものがあります。Ancient Cには union キーワードが存在しませんが、このような(標準Cから見ると変わった)仕様により代替していたようです。

ancient-common.c
struct file {
  int  type;     /* OK */
  char name[8];  /* OK */
  int  size;
};

struct dir {
  int  type;     /* OK */
  char name[8];  /* OK */
  struct file *flist;
};

構造体メンバへのアクセス

標準Cと同じく、Ancient Cにも構造体メンバアクセス演算子 .-> が存在します。それぞれ "C Reference Manual" §7.1.7 primary-lvalue . member-of-structure, §7.1.8 primary-expression -> member-of-structure を部分引用します。ちなみにセクション名=構文規則ですね。ワイルド。

An lvalue expression followed by a dot followed by the name of a member of a structure is a primary expression. The object referred to by the lvalue is assumed to have the same form as the structure containing the structure member. The result of the expression is an lvalue appropriately offset from the origin of the given lvalue whose type is that of the named structure member. The given lvalue is not required to have any particular type.
(参考訳) 構造体メンバの名前に先行するドットに先行する左辺値(lvalue)式はプライマリ(primary)式である。左辺値の指すオブジェクトは、その構造体メンバを含む構造体と同形式とみなされる。式の結果は所与の左辺値構造体の基点から適切にオフセットされた左辺値となる。所与の左辺値には特定の型が要求されない。

The primary-expression is assumed to be a pointer which points to an object of the same form as the structure of which the member-of-structure is a part. The result is an lvalue appropriately offset from the origin of the pointed to structure whose type is that of the named structure member. The type of the primary-expression need not in fact be pointer; it is sufficient that it be a pointer, character, or integer.
(参考訳) プライマリ式(primary-expression)は構造体のメンバ(member-of-structure)を含む構造体と同形式のオブジェクトを指すポインターとみなされる。結果は指し先構造体の基点から適切にオフセットされた 左辺値(lvalue)であり、その型は構造体メンバの型となる。プライマリ式の型はポインタである必要もない; ポインタや文字、または整数値であってもよい。

両者とも「同演算子の左辺の型は不問」とあります。これはつまり、Ancient Cでは メンバアクセス演算子(., ->)の右辺(メンバ名)から構造体型が決まる という仕様なのです。演算子の左辺では、そのように決まった構造体型をさす左辺値(lvalue)やポインタ(pointer)へと勝手に読みかえられます。

ancient-struct.c
/* 無名の構造体 */
struct {
  int key
  int value;
};

read_value(addr)
/* addrは暗黙にint型 */
{
  int a;
  /* 引数addrの値は無名構造体へのポインタと解釈され、
   * メンバvalueの位置からメモリ内容を読み取る。
   */
  a = addr->value;
}

標準Cであれば型キャスト(type cast)を用いるべき場面ですが、Ancient Cにはそもそも キャスト構文は存在しません。これほど柔軟に構造体型を相互変換できれば、明示的な型キャストなどもはや不要でしょう。コンパイル時の型検査なにそれ?おいしいの?

適当に明日以降につづきます。


  1. 同仕様の残り香としてか、ANSI C/C90言語仕様には "the members of structures or unions; each structure or union has a separate name space for its members (disambiguated by the type of the expression used to access the member via the . or -> operator);" という言及があります。