C言語/構造体

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

構造体の基本[編集]

構造体(こうぞうたい; structure)は、構造体は、記憶域が順番に割り当てられたメンバーの列からなる型です [1]。 複合的な方という意味では配列と似ていますが、配列の要素はすべて同じ型で添字によってアクセスするのに対し、構造体のメンバーは任意の型を取ることが出来、メンバー名によるアクセスするところが異なります。 また、構造体の配列を作ることも出来ます。

たとえば、商品名と価格の2つ組を扱いたいときには、char * 商品名int 価格 からなるデーター構造が考えられます。

商品1
商品名
牛乳
価格
200
商品2
商品名
オレンジジュース
価格
150

構造体の定義[編集]

上述の商品名と価格の2つ組の例をC言語のコードでは、

構造体の定義
struct ProductEntry {
  char *name; // 商品名
  int price;  // 価格
};

もように表すことが出来ます。

また、上記の例の ProductEntry のように構造体に付ける名前は、構造体タグ( structure tags )と呼ばれます[2]

構造体は1個以上のメンバー( member )を持ちます。上の struct ProductEntry の例では nameprice がメンバーです。

構造体変数[編集]

構造体を定義しても、構造体を型として持つの変数 (以下、構造体変数と呼ぶ)は作られません。 構造体を利用するには、構造体の構造体変数( structure variable )を宣言します。

構造体変数の宣言と初期化
#include <stdio.h>

struct ProductEntry {
  char *name;
  int price;
};

void print_product_entry(const struct ProductEntry product) {
  printf("商品名 : %s, 価格 : %d 円\n", product.name, product.price);
}

int main(void) {
  const struct ProductEntry milk = {"牛乳", 200},
                    orange_juice = {"オレンジジュース", 150};
  print_product_entry(milk);
  print_product_entry(orange_juice);
}
実行結果
商品名 : 牛乳, 価格 : 200 円
商品名 : オレンジジュース, 価格 : 150 円

構造体変数を宣言するときは、上述の struct ProductEntry milk; のように宣言します。

また、構造体変数の要素に代入したい場合など、構造体変数を参照したい場合には、「.」(ドット演算子)を用いて、milk.name のように記述します[3]

形式
構造体名 . 構造体変数名

になります[4]

構造体変数の初期化[編集]

構造体変数は、宣言と同時に、値のリストで初期化することができます。

形式
struct タグ名 変数名 = {値のリスト};

値のリストは「,」(コンマ)で区切ったリストです。

構造体変数の初期化
int main(void) {
  struct {
    int i;
    double d;
    char c;
    const char *str;
  } my_struct = {1234, 3.14, 'a', "Hello, World!"};
}
上の例では、my_struct.i を1234、my_struct.d を3.14、my_struct.c を'a'、my_struct.str を"Hello, World!"で初期化しています。
メンバーを指定した構造体の初期化
int main(void) {
  struct {
    int i;
    double d;
    char c;
    const char *str;
  } my_struct = {.str = "Hello, World!", .c = 'a', .i = 1234, .d = 3.14};
}
C99 の機能を使用すると、このように構造体メンバーを任意の順序で初期化することができます。
複合リテラルを使った例
int main(void) {
  struct MyStruct {
    int i;
    double d;
    char c;
    const char *str;
  };
  struct MyStruct my_struct;
  my_struct = (struct MyStruct){.str = "Hello, World!", .c = 'a', .i = 1234, .d = 3.14};
}

複合リテラルを使うと、初期化のコンテキストでなくても(キャストに似た構文で)リテラルによる一括代入が出来ます。 ただ、複合リテラルはスコープの中で一度しか生成されないので、ループの内側で使うと期待とは違った値を得る結果になります。

コード例
(省略)

構造体へのアクセス[編集]

構造体のメンバーにアクセスするには、 「.(ドット)演算子」を用いて、次のように記述します。

形式
構造体の変数名 . メンバー名
構造体の読み取り
#include <stdio.h>

int main(void) {
  struct {
    int i;
    double d;
    char c;
    char str[32 + 1];
  } my_struct;

  printf("整数を入力してください。:");
  if (scanf("%d", &my_struct.i) == EOF) { // 整数入力を my_structure.i に格納します。
    printf("End of File に達しました。\n"); 
    return 1;
  }
  printf("浮動小数点数を入力してください。:");
  if (scanf("%lf", &my_struct.d) == EOF) { // 浮動小数点数入力を my_structure.d に格納します。
    printf("End of File に達しました。\n"); 
    return 1;
  }
  printf("文字(半角1文字)を入力してください。:");
  if (scanf("%c", &my_struct.c) == EOF) { // 文字入力を my_structure.c に格納します。
    printf("End of File に達しました。\n"); 
    return 1;
  }
  printf("文字列(半角31文字、全角15文字まで)を入力してください。:");
  if (scanf("%32s", my_struct.str)) { // 文字列入力を my_structure.str に格納します。
    printf("End of File に達しました。\n"); 
    return 1;
  }
  printf("my_structのメンバーの値は、%d %f %c %sです。\n", my_struct.i,
         my_struct.d, my_struct.c, my_struct.str);
}
上の例では、ユーザーからの4つの入力を構造体の4つのメンバーに格納し、その4つのメンバーの値を表示しています。
※ 過去の編集で、9行目が const char * str;となっていましたが、未初期化のポインター変数による書き込み参照を生じていました。

構造体の代入[編集]

配列とは異なり、構造体は一度に代入することができます。 構造体を代入する時は、構造体の変数名のみを用います。

形式
構造体の変数名 = 構造体の変数名;

これにより、右の構造体の全てのメンバーが左の構造体の全てのメンバーに代入されますが、以下の点に注意する必要があります。

漏洩への配慮
代入の右辺の構造体に隙間が含まれていることがある。その隙間に過去に使われた時の値があり、それらも代入されます。
そのため、代入の左辺の構造体が外部からアクセス可能な場合、情報漏洩に繋がるリスクがある[5]
型の一致
代入の右辺の構造体と代入の左辺の構造体は、構造体の型が一致していなければいけません。
浅いコピー(シャローコピー)
ここでいう「構造体の代入」は浅いコピー(シャローコピー)です。そのため、メンバーにポインターが含まれている場合そのアドレスだけがコピーされ、その指している先は代入の右辺と同一になります。これによって、片方の構造体のポインターが指している先を変更すると、もう片方の構造体のポインター先の値まで変わってしまう。
構造体の代入
#include <stdint.h>
#include <stdio.h>

int main(void) {
  typedef struct {
    const char *str;
    double d;
    int32_t i;
    char c;
  } __attribute__((__packed__)) my_struct_t;

  my_struct_t my_struct_1 = {"Hello, World!", 3.14, 1234, 'a'}, my_struct_2;

  my_struct_2 = my_struct_1;
  printf("代入の左辺のメンバーの値は以下のとおりです:\n");
  printf("\n");
  printf("  my_struct_2.str: \"%s\"\n", my_struct_2.str);
  printf("  my_struct_2.d  : %f\n", my_struct_2.d);
  printf("  my_struct_2.i  : %d\n", my_struct_2.i);
  printf("  my_struct_2.c  : '%c'\n", my_struct_2.c);
  printf("\n");
  printf("また、構造体 my_struct_t のサイズは %zu バイトです。\n",
         sizeof(my_struct_t));
}
__attribute__((__packed__))

構造体のメンバーは、プログラムテキストで宣言された順序でメモリー上に配置されます。 構造体のメンバーがメモリー上で適切にアライメントされるように、構造体にパディング(詰め物)を加えることがあります。 多くのアーキテクチャでは、構造体パディングをすることで構造体のメンバーへのアクセスが高速化しますし、アライメントに不整合があるとバスエラーを生じてプログラムが中断することすらあります。 __attribute__((__packed__)) はGCC の拡張機能で、パディングを挿入しないように指示し構造体メンバーの位置をずらすことを可能にします。 本節では、構造体パッディング(に残った値)が情報漏洩につながると論じており、__attribute__((__packed__)) を用いてパッディングの削減を図っています。

単体の構造体同士ではこの手法も有効ですが、構造体の配列ではアライメントの要請からパディングを生じるので __attribute__((__packed__)) も万能ではなく、構造体同士の代入ではなく要素間の代入をする関数を用意しそれを使うなどの根本的な解決方法が望まれます。

そもそも上記の例では、my_struct_1 も my_struct_2 もスタック上のインスタンスなので驚異の質は同じで脆弱性をうまく表現できていません。

typedefによるコードの短縮[編集]

typedefを構造体に用いて、コードを短縮することができます。

typedefを用いない構造体の宣言
struct my_struct {
	int i;
	double d;
	char c;
    char str[32 + 1];
};

int main(void) {
	struct my_struct my_var; // structキーワードが必要
}

上の例は typedef を用いて、下の例のように記述することができます。

typedefを用いた構造体の宣言
typedef struct {
	int i;
	double d;
	char c;
    char str[32 + 1];
} my_type;

int main(void) {
	my_type my_var; // structキーワードは不要
}

おそらく、いちばん有名な typedef された構造体は、 <stdio.h> で定義されている FILE でしょう。

構造体の応用[編集]

構造体を引数に持つ関数[編集]

構造体を関数の引数にしたい場合、下記のように行います。

構造体を関数の引数にする
#include <stdio.h>

typedef struct {
  char* name;
  int price;
} product_entry;

void print_product_entry(const product_entry product) {
  printf("商品名 : %s , 価格 : %d 円\n", product.name, product.price);
}

int main(void) {
  // 構造体の設定
  product_entry milk = {"牛乳", 200},
        orange_juice = {"オレンジジュース", 150};

  // 関数のテスト
  print_product_entry(milk);
  print_product_entry(orange_juice);
  print_product_entry((product_entry){"トマトジュース", 130});
  print_product_entry((product_entry){"グレープジュース", 180});
}

解説[編集]

  • 構造体の定義は、構造体変数(や仮引数)を使うより前に行います。
  • typedef で定義した型 product_entry を使うときには、structキーワードは必要ありません。
  • 20, 21行目は、C99の新機能 複合リテラル( Compound Literal )を使った例です[6]
Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合

Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合は、

[ツール] -> [オプション] -> [デバッグ] -> [デバッグの停止時に自動的にコンソールを閉じる]

を無効にします。

構造体の配列[編集]

上の例では商品が2個しかなかったから、都度構造体変数を宣言すればよかった。しかし、商品が100個になったり、数が不定になったりしたらどうだろうか?個々の変数に割り当てる方法では厳しくなってくる。 そこで、構造体の配列を宣言し使用することでその問題に対処する。

構造体の配列を作る
#include <stdio.h>

typedef struct {
  char* name;
  int price;
} product_entry;

int main(void) {
  const product_entry products[] = {{"牛乳", 200},
                                    {"オレンジジュース", 150},
                                    {"トマトジュース", 180},
                                    {"りんごジュース", 220}};

  for (size_t i = 0; i < sizeof(products) / sizeof(products[0]); i++) {
    printf("商品名: %s, 価格: %d円\n", products[i].name, products[i].price);
  }
}
実行結果
商品名: 牛乳, 価格: 200円
商品名: オレンジジュース, 価格: 150円
商品名: トマトジュース, 価格: 180円
商品名: りんごジュース, 価格: 220円

書式[編集]

構造体の配列を宣言する時は、変数名の後に「[要素数]」をつけます。

形式
struct タグ名 配列の変数名 [ 要素数 ];

構造体の配列のメンバーにアクセスする時は、変数名の後に[添字]をつけます。

形式
配列の変数名 [ 添字 ] . メンバー名

[]演算子と.演算子は、共に同じ優先順位の演算子で、左から右へと評価されるため (左結合)、次の式と等価です。

( 配列の変数名 [ 添字 ] ) . メンバー名
構造体の配列
#include <stdio.h>

#define N_ARRAY(ary) (sizeof(ary) / sizeof(ary[0]))

int main(void) {
  struct {
    int i;
    double d;
    char c;
    char* str;
  } array[] = {
      {12, 1.2, 'a', "abc"},
      {34, 3.4, 'b', "def"},
      {56, 5.6, 'c', "ghi"},
      {78, 7.8, 'd', "jkl"},
      {.str = NULL}, // 終端
  };

  for (int i = 0; array[i].str != NULL && i < N_ARRAY(array); i++) {
    printf("struct_array[%i]のメンバーの値は、"
           "{ .i = %d, .d = %f, .c = %c, .str = %s }です。\n",
           i, array[i].i, array[i].d, array[i].c, array[i].str);
  }
}

上の例では構造体の配列に初期値を与え、そのメンバーの値を表示しています。 メンバー .str にNULLがセットされた要素が終端を示します。 この例では、配列の大きさを N_ARRAY() で得ることが出来ますが、関数呼び出しの引数に渡された時には呼ばれた関数では長さがわからないので終端の約束事を決める必要があります。 これは、C言語の配列プログラムを扱うプログラムでは広く使われる手法で、Hello World でも。

構造体の配列
    printf("Hello World!\n");

の "Hello World!\n" も '\0' で終端された文字型の配列を引数にしています。

構造体を関数の引数として渡す[編集]

構造体を関数の引数として渡す例
#include <stdio.h>

#define N_ARRAY(ary) (sizeof(ary) / sizeof(ary[0]))

typedef struct {
  char *name;
  int price;
} product_entry;

void func1(const product_entry products) {
  printf("商品名: %s, 価格: %d円\n", products.name, products.price);
}

int main(void) {
  const product_entry products[] = {{"牛乳", 200},
                                    {"オレンジジュース", 150},
                                    {"トマトジュース", 180},
                                    {"りんごジュース", 220},
                                    {.name = NULL}};

  func1(products[2]);
}
実行結果
商品名: トマトジュース, 価格: 180円

上記コード例の場合、関数定義側(func1 )の仮引数では配列を使ってない事に注意。上記コードのような用途の場合なら呼出側で配列を使えば十分です。


さらなる応用・発展として、構造体のネストの配列の関数や、構造体の多重配列の関数など、色々と考えられるが、しかし似たような説明の繰り返しになるので本ページではこれ以上の深入りは省略する。原理的には、上記までに習った個々の事例を組み合わせことでプログラミングできるはずである。 --->

構造体のネスト[編集]

構造体のメンバーに、構造体を指定することができます。 本項ではこれを「構造体のネスト」と呼ぶことにします。

ネストされた構造体にアクセスする時は、ネストの最も外側から内側に向かって参照しなければなりません。

例11. 「構造体のネスト」
#include <stdio.h>

int main(void) {
  struct my_struct1 {
    int i;
    double d;
    char c;
  };

  struct my_struct2 {
    int i;
    double d;
    char c;
    struct my_struct1 s;
  } my_struct = {
      1234, 1.23, 'a', {5678, 5.67, 'b'}};

  printf("%d %f %c\n", my_struct.i, my_struct.d, my_struct.c);
  printf("%d %f %c\n", my_struct.s.i, my_struct.s.d, my_struct.s.c);
}
結果
1234 1.230000 a 
5678 5.670000 b

上の例では、my_structのメンバーであるsが「構造体のネスト」です。

構造体配列と構造体配列へのポインター[編集]

商品リストの話に戻ります。 一つの店舗だけでなく複数の店舗の(可変長の)商品リストを扱う場合はどうすればよいでしょう? 1つのやり方として十分大きな配列で確保する方法がありますが、自由領域に長大な(疎な)オブジェクトを置くことになりメモリー(より厳密にいうとスタック領域)を使い果たしてしまう可能性があります。 そこで、複合リテラルで構造体配列を用意することにします。

構造体配列と構造体配列へのポインター
#include <stdio.h>

typedef struct {
  char *name;
  int price;
} product_entry;

typedef struct {
  char *name;
  product_entry *products;
} store;

int main(void) {
  store stores[] = {{"ウィキペディア店",    (product_entry[]){{"牛乳", 200}, {NULL,0}}},
                    {"ウィクショナリー店",  (product_entry[]){{"牛乳", 250}, {NULL,0}}},
                    {"ウィキブックス店",    (product_entry[]){{"牛乳", 170}, {"オレンジジュース", 190}, {NULL,0}}},
                    {"ウィキソース店",      (product_entry[]){{"牛乳", 210}, {"青汁", 540}, {NULL,0}}},
                    {"ウィキクォート店",    (product_entry[]){{"牛乳", 190}, {NULL,0}}},
                    {NULL}};

  for (int i = 0; stores[i].name != NULL; i++) {
    const store *st = stores + i;
    printf("名前: %s, 商品:\n", st->name);
    for (int j = 0; st->products[j].name != NULL; j++) {
      const product_entry *pr = st->products + j;
      printf("  名前: %s, 値段: %d 円", pr->name, pr->price);
    }
    printf("\n");
  }
}

複合リテラルで構造体の配列を作り、特定のメンバーを(文字列の '\0'のように)終端の意味に使い、長さの違う配列を扱っています。

二重配列の構造体変数[編集]

構造体配列は、二重配列であっても良い。つまり、構造体変数には、二重配列を宣言する事も可能です。

よって構造体のネストを使わなくても、下記コードのように二重配列でも代用できます。

コード例
#include <stdio.h>

struct result {
  char* name;
  int score_japanese;
  int score_mathematics;
};

int main(void) {
  struct result student[3][2]; // 構造体配列の宣言   [組][組内番号]

  student[0][0] = (struct result){"山田123", 80, 70};
  student[0][1] = (struct result){"佐藤", 60, 90};
  student[1][0] = (struct result){"安部", 78, 65};
  student[1][1] = (struct result){"小泉", 50, 100};

  for (int klasse = 0; klasse <= 1; klasse++) {
    for (int number = 0; number < 2; number++) {
      struct result *p = &student[klasse][number];
      printf("名前: %s, 国語: %d点, 数学: %d点\n", 
             p->name,
             p->score_japanese,
             p->score_mathematics);
    }
  }
}
実行結果
名前: 山田123, 国語: 80点, 数学: 70点
名前: 佐藤, 国語: 60点, 数学: 90点
名前: 安部, 国語: 78点, 数学: 65点
名前: 小泉, 国語: 50点, 数学: 100点

構造体へのポインター[編集]

構造体へのポインターを宣言する時は、変数名の前に「*」をつけます。

形式
struct タグ名 * ポインターの変数名 ;

ポインターの指す構造体のメンバーにアクセスする時は、変数名の前に「*」をつけ、「.」の方が優先順位が高いため「()」で囲みなす。

形式
( * ポインターの変数名 ) . メンバー名


また「->」(アロー演算子)を用いて次のように記述できます。

形式
ポインターの変数名 -> メンバー名
//例 構造体へのポインター
#include <stdio.h>
#include <string.h>

int main(void) {
  struct {
    int i;
    double d;
    char c;
    char str[32];
  } my_struct, *pmy_struct;

  pmy_struct = &my_struct;

  pmy_struct->i = 1234;
  pmy_struct->d = 3.14;
  pmy_struct->c = 'a';
  strcpy(pmy_struct->str, "Hello, World!");

  printf("my_structのメンバーの値は、%d %f %c %sです。\n", pmy_struct->i,
         pmy_struct->d, pmy_struct->c, pmy_struct->str);
}

上の例では、構造体へのポインターを使って構造体のメンバーに値を代入したり、その値を表示したりしています。

なお、strcpyは、文字列をコピーするための関数。strcpyを使うには、<string.h> のインクルードが必要です。

また、文字列の宣言は、(上記の char str[32]; のように)文字の配列として宣言するのが一般的です。

構造体のポインターを引数にした関数[編集]

大きな構造体を引数に指定すると、実引数から仮引数へその構造体全体をコピーするため時間がかかってしまいます。 高速化したい場合、構造体を引数として呼び出す際に構造体をコピーせずに(仮引数として)参照できるようにすれば良いのでポインターを活用する必要があります。 構造体へのポインターを引数に指定すると、引数のコピーをせずに元々の構造体を参照するのでコピーの時間が省略されるしメモリーも節約されるので高速に処理できます。 このような処理において、構造体を指すポインターのメンバーにアクセスする場合には、(前の節で上述した)アロー演算子を使います。

関数に構造体のポインターを引数にした例
//例 引数に大きな構造体を指定する
#include <stdio.h>

//この構造体が非常に大きな構造体であるとする
typedef struct {
  int i;
  double d;
  char c;
  char str[32000];
} smy_struct;

void function(const smy_struct *my_struct) { //構造体へのポインターを引数に指定する
  printf("my_structのメンバーの値は、%d %f %c %sです。\n", my_struct->i,
         my_struct->d, my_struct->c, my_struct->str);
}

int main(void) {
  smy_struct my_struct = {1234, 3.14, 'a', "Hello World!"};
  function(&my_struct);
}

function(smy_struct *my_struct) がポインターで呼び出せるようにするために、渡す側 function(&my_struct) は &演算子を使いアドレスを渡しています。

メンバーに同じ型へのポインターを持つ構造体[編集]

自分自身と同じ型へのポインターを構造体のメンバーにできます。 このような構造体を自己参照構造体( self-referential structure )と呼びます。

自己参照構造体の例
#include <stdio.h>

int main(void) {
  struct smy_struct {
    struct smy_struct *next;
    int i;
    double d;
    char c;
    char str[8];
  };

  struct smy_struct a = {NULL, 123, 1.23, 'a', "abc"}, 
                    b = {&a, 456, 4.56, 'b', "def"},
                    c = {&b, 789, 7.89, 'c', "ghi"};

  for (struct smy_struct *p = &c; p; p = p->next)
    printf("%d %f %c %s\n", p->i, p->d, p->c, p->str);
}
実行結果
789 7.890000 c ghi
456 4.560000 b def
123 1.230000 a abc

上の例では、3つの構造体a、b、cのメンバーをポインター変数pを用いてトラバースしながら表示しています。


脚註[編集]

  1. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 81, §6.7.2.1 Structure and union specifiers. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  2. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 85, §6.7.2.3 Tags. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  3. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 59, §6.5.2.3 Structure and union members. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  4. ^ N2176 C17 ballot ISO/IEC 9899:2017. ISO/IEC JTC1/SC22/WG14. p. 57, §6.5.2 Postfix operators. オリジナルの2018-12-30時点によるアーカイブ。. https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf. 
  5. ^ 構造体 –漏洩させないための注意点–
  6. ^ N1570 Committee Draft — April 12, 2011 ISO/IEC 9899:201x. ISO/IEC. p. 85, §6.5.2.5 Compound literals. http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf.