構造体宣言
構造体は、記憶域が順番通りに連続的に確保される、一連のメンバから構成される型です (記憶域がオーバーラップする一連のメンバから構成される型である共用体とは対照的です)。
構造体のための型指定子は、使用されるキーワード以外は、共用体の型指定子と同じです。
目次 |
[編集] 構文
struct name(オプション) { struct-declaration-list }
|
(1) | ||||||||
struct name
|
(2) | ||||||||
struct
name ;
のように独立した宣言として使用された場合は、構造体 name
を宣言しますが、定義しません (下の前方宣言を参照してください)。 それ以外の文脈では、以前に宣言された構造体を表します。name | - | 定義中の構造体の名前。 |
struct-declaration-list | - | 任意個の変数宣言、ビットフィールド宣言、および static assert 宣言。 不完全型のメンバおよび関数型のメンバは使用できません (下で説明するフレキシブル配列メンバの場合は除きます)。 |
[編集] 説明
構造体のオブジェクト内では、その要素のアドレス (およびビットフィールドの確保単位のアドレス) は、メンバが定義された順序で増加します。 構造体へのポインタは、その最初のメンバ (または、そのメンバがビットフィールドの場合は、その確保単位) へのポインタにキャストできます。 同様に、構造体の最初のメンバへのポインタは、囲っている構造体へのポインタにキャストできます。 構造体の2つのメンバの間および最後のメンバの後に、無名のパディングが存在する場合がありますが、最初のメンバの前にはありません。 構造体のサイズは、少なくともそのメンバのサイズの合計か、それより大きくなります。
構造体が名前付きのメンバを少なくともひとつ定義する場合は、その最後のメンバを不完全配列型で宣言することができます (フレキシブル配列メンバと言います)。 フレキシブル配列メンバの要素がアクセスされたとき (フレキシブル配列メンバの名前を右側の被演算子として struct s { int n; double d[]; }; // s.d はフレキシブル配列メンバです。 struct s t1 = { 0 }; // OK、 t1.d は double[1] であるかのように振る舞います (ただし要素にアクセスすると未定義動作です)。 struct s t2 = { 1, { 4.2 } }; // エラー、初期化はフレキシブル配列メンバを無視します。 // sizeof (double) == 8 の場合 struct s *s1 = malloc(sizeof (struct s) + 64); // s1->d は double[8] であるかのように振る舞います。 struct s *s2 = malloc(sizeof (struct s) + 40); // s2->d は double[5] であるかのように振る舞います。 s1 = malloc(sizeof (struct s) + 10); // 以降 s1->d は double[1] であるかのように振る舞います。 2バイト余っています。 double *dp = &(s1->d[0]); // OK。 *dp = 42; // OK。 s1->d[1]++; // 未定義動作。 余りの2バイトは // double としてアクセスできません。 s2 = malloc(sizeof (struct s) + 6); // 以降 s2->d は double[1] であるかのように振る舞いますが、 // 2バイトは完全な double 1個には足りないため、アクセスは未定義動作です。 dp = &(s2->d[0]); // OK、アドレスを取ることは問題ありません。 *dp = 42; // 未定義動作。 *s1 = *s2; // s.n のみがコピーされます。 // sizeof (struct s) に含まれる分を除いて s.d の要素はコピーされません。 |
(C99以上) |
共用体と同様に、 name を持たない構造体型のメンバは、無名構造体と言います。 無名構造体のすべてのメンバは、その囲っている構造体または共用体のメンバとみなされます。 囲っている構造体または共用体も無名であれば、これは再帰的に適用されます。 struct v { union { // 無名共用体 struct { int i, j; }; // 無名構造体 struct { long k, l; } w; }; int m; } v1; v1.i = 2; // 有効。 v1.k = 3; // 無効。 内側の構造体が無名ではありません。 v1.w.k = 5; // 有効。 共用体と同様に、名前付きのメンバ (ネストした無名構造体または無名共用体から取得したものを含みます) を一切持たずに構造体が定義された場合、プログラムの動作は未定義です。 |
(C11以上) |
[編集] 前方宣言
以下の形式の宣言は、
struct name ;
|
|||||||||
タグ名前空間内の名前 name に対するそれまでに宣言されたあらゆる意味を隠蔽し、後に定義されるであろう新しい構造体の名前として、 name を現在のスコープに宣言します。 その定義が現れるまでは、その構造体の名前は不完全型です。
これにより、構造体を相互に参照することが可能となります。
struct y; struct x { struct y *p; /* ... */ }; struct y { struct x *q; /* ... */ };
別の宣言内で構造体タグを用いることによっても新しい構造体の名前を導入できますが、以前に宣言された同じ名前を持つ構造体がタグ名前空間に存在する場合は、その構造体が参照されることに注意してください。
struct s* p = NULL; // 未知の構造体を表すタグがそれを宣言します。 struct s { int a; }; // p の指す先の構造体に対する定義。 void g(void) { struct s; // 新しいローカルな構造体 s の前方宣言。 // これはこのブロックの終わりまでグローバルな構造体 s を隠蔽します。 struct s *p; // ローカルな構造体 s へのポインタ。 // もし直前の前方宣言がなければ、 // これはファイルスコープの s を指したでしょう。 struct s { char* p; }; // ローカルな構造体 s の定義。 }
[編集] キーワード
[編集] ノート
構造体の初期化に関するルールについては構造体の初期化を参照してください。
不完全型のメンバは使用できず、構造体型は定義の終わりまでは完全でないため、構造体は自分自身の型のメンバを持つことはできません。 自分自身の型へのポインタは使用でき、これは連結リストやツリーでノードを実装するためによく使用されます。
構造体の宣言はスコープを確立しないため、 struct-declaration-list 内の宣言によって導入されたネストした型、列挙、および列挙子は、その構造体が定義された周りのスコープでも可視です。
[編集] 例
#include <stddef.h> #include <stdio.h> int main(void) { struct car { char *make; char *model; int year; }; // 構造体型を宣言します。 // 上で宣言した構造体型のオブジェクトを宣言し、初期化します。 struct car c = {.year=1923, .make="Nash", .model="48 Sports Touring Car"}; printf("car: %d %s %s\n", c.year, c.make, c.model); // 構造体型、その型のオブジェクト、およびそれへのポインタを宣言します。 struct spaceship { char *make; char *model; char *year; } ship = {"Incom Corporation", "T-65 X-wing starfighter", "128 ABY"}, *pship = &ship; printf("spaceship: %s %s %s\n", ship.year, ship.make, ship.model); // アドレスは定義の順序で増加します。 // パディングが挿入されることがあります。 struct A { char a; double b; char c;}; printf("offset of char a = %zu\noffset of double b = %zu\noffset of char c = %zu\n" "sizeof(struct A)=%zu\n", offsetof(struct A, a), offsetof(struct A, b), offsetof(struct A, c), sizeof(struct A)); struct B { char a; char b; double c;}; printf("offset of char a = %zu\noffset of char b = %zu\noffset of double c = %zu\n" "sizeof(struct B)=%zu\n", offsetof(struct B, a), offsetof(struct B, b), offsetof(struct B, c), sizeof(struct B)); // 構造体へのポインタはその最初のメンバへのポインタにキャストできます (逆もできます)。 char* pmake = (char*)&ship; pship = (struct spaceship *)pmake; }
出力例:
car: 1923 Nash 48 Sports Touring Car spaceship: 128 ABY Incom Corporation T-65 X-wing starfighter offset of char a = 0 offset of double b = 8 offset of char c = 16 sizeof(struct A)=24 offset of char a = 0 offset of char b = 1 offset of double c = 8 sizeof(struct B)=16