サーチ…
前書き
構造体は、多様な型の関連変数のセットを単一のメモリ単位にグループ化する方法を提供します。構造全体は単一の名前またはポインタで参照できます。構造部材にも個別にアクセスすることができます。構造体は関数に渡され、関数から返されます。それらはキーワードstruct
を使用して定義されます。
単純なデータ構造
構造体データ型は、関連するデータをパッケージ化し、単一の変数のように動作させるのに便利です。
2つのint
メンバーを保持する単純なstruct
を宣言する:
struct point
{
int x;
int y;
};
x
とy
はpoint
構造体のメンバ (またはフィールド )と呼ばれます。
構造体の定義と使用:
struct point p; // declare p as a point struct
p.x = 5; // assign p member variables
p.y = 3;
構造体は定義時に初期化できます。上記は次のものと同じです:
struct point p = {5, 3};
構造体は、 指定された初期化子を使用して初期化することもできます 。
フィールドへのアクセスは、 .
オペレーター
printf("point is (x = %d, y = %d)", p.x, p.y);
Typedef構造体
struct
とtypedef
を組み合わせることで、コードをより明確にすることができます。例えば:
typedef struct
{
int x, y;
} Point;
とは対照的に:
struct Point
{
int x, y;
};
次のように宣言できます。
Point point;
の代わりに:
struct Point point;
次のものを使うのも良い
typedef struct Point Point;
struct Point
{
int x, y;
};
可能なpoint
定義の両方を利用すること。このような宣言は、C ++を最初に学んだ場合に最も便利です。名前があいまいでない場合は、 struct
キーワードを省略することができます。
構造体のtypedef
名は、プログラムの他の部分の他の識別子と競合する可能性があります。これは不利な点があると考えている人もいますが、 struct
と別の識別子を持つほとんどの人にとっては、同じことがかなり邪魔になります。 Notoriousは例えばPOSIXのstat
int stat(const char *pathname, struct stat *buf);
struct stat
という1つの引数を持つ関数stat
が表示されます。
タグ名を持たないtypedef
構造体は、 struct
宣言全体がそれを使用するコードから見えるようにしなければなりません。 struct
宣言全体をヘッダファイルに配置する必要があります。
検討してください:
#include "bar.h"
struct foo
{
bar *aBar;
};
したがって、タグ名を持たないtypedef
struct
では、 bar.h
ファイルは常にbar
全定義を含む必要がありbar
。私たちが使うなら
typedef struct bar bar;
bar.h
では、 bar
構造の詳細を隠すことができます。
typedefを参照してください
構造体へのポインタ
struct
を含む変数がある場合は、ドット演算子( .
)を使用してそのフィールドにアクセスできます。しかし、 struct
へのポインタがあれば、これは機能しません。フィールドにアクセスするには、矢印演算子( ->
)を使用する必要があります。ここでは、 struct
のポインタを使用して矢印演算子を示すスタックの非常に単純な(たぶん "恐ろしい単純な")実装の例を示します。
#include <stdlib.h>
#include <stdio.h>
/* structs */
struct stack
{
struct node *top;
int size;
};
struct node
{
int data;
struct node *next;
};
/* function declarations */
int push(int, struct stack*);
int pop(struct stack*);
void destroy(struct stack*);
int main(void)
{
int result = EXIT_SUCCESS;
size_t i;
/* allocate memory for a struct stack and record its pointer */
struct stack *stack = malloc(sizeof *stack);
if (NULL == stack)
{
perror("malloc() failed");
return EXIT_FAILURE;
}
/* initialize stack */
stack->top = NULL;
stack->size = 0;
/* push 10 ints */
{
int data = 0;
for(i = 0; i < 10; i++)
{
printf("Pushing: %d\n", data);
if (-1 == push(data, stack))
{
perror("push() failed");
result = EXIT_FAILURE;
break;
}
++data;
}
}
if (EXIT_SUCCESS == result)
{
/* pop 5 ints */
for(i = 0; i < 5; i++)
{
printf("Popped: %i\n", pop(stack));
}
}
/* destroy stack */
destroy(stack);
return result;
}
/* Push a value onto the stack. */
/* Returns 0 on success and -1 on failure. */
int push(int data, struct stack *stack)
{
int result = 0;
/* allocate memory for new node */
struct node *new_node = malloc(sizeof *new_node);
if (NULL == new_node)
{
result = -1;
}
else
{
new_node->data = data;
new_node->next = stack->top;
stack->top = new_node;
stack->size++;
}
return result;
}
/* Pop a value off of the stack. */
/* Returns the value popped off the stack */
int pop(struct stack *stack)
{
struct node *top = stack->top;
int data = top->data;
stack->top = top->next;
stack->size--;
free(top);
return data;
}
/* destroy the stack */
void destroy(struct stack *stack)
{
/* free all pointers */
while(stack->top != NULL)
{
pop(stack);
}
}
柔軟な配列メンバー
型宣言
少なくとも1つの部材を有する構造体は、構造体の端部に不特定の長さの単一の配列部材をさらに含むことができる。これはフレキシブルな配列メンバーと呼ばれます:
struct ex1
{
size_t foo;
int flex[];
};
struct ex2_header
{
int foo;
char bar;
};
struct ex2
{
struct ex2_header hdr;
int flex[];
};
/* Merged ex2_header and ex2 structures. */
struct ex3
{
int foo;
char bar;
int flex[];
};
サイズとパディングに対する影響
フレキシブルな配列メンバは、構造体のサイズを計算するときにサイズを持たないものとして扱われますが、そのメンバと構造体の前のメンバとの間のパディングはまだ存在するかもしれません:
/* Prints "8,8" on my machine, so there is no padding. */
printf("%zu,%zu\n", sizeof(size_t), sizeof(struct ex1));
/* Also prints "8,8" on my machine, so there is no padding in the ex2 structure itself. */
printf("%zu,%zu\n", sizeof(struct ex2_header), sizeof(struct ex2));
/* Prints "5,8" on my machine, so there are 3 bytes of padding. */
printf("%zu,%zu\n", sizeof(int) + sizeof(char), sizeof(struct ex3));
フレキシブル配列メンバーは不完全な配列型であるとみなされるため、 sizeof
を使用してそのサイズを計算することはできません。
使用法
フレキシブルな配列メンバーを含む構造体タイプのオブジェクトを宣言して初期化することはできますが、フレキシブルな配列メンバーが存在しないかのように処理されるため、フレキシブルな配列メンバーを初期化しないでください。これを行うことは禁じられ、コンパイルエラーが発生します。
同様に、この方法で構造体を宣言するときは、フレキシブル配列メンバの任意の要素に値を代入してはいけません。しかし、コンパイラは必ずしもこれを行うことを妨げません。そのため、未定義の動作につながる可能性があります。
/* invalid: cannot initialize flexible array member */
struct ex1 e1 = {1, {2, 3}};
/* invalid: hdr={foo=1, bar=2} OK, but cannot initialize flexible array member */
struct ex2 e2 = {{1, 2}, {3}};
/* valid: initialize foo=1, bar=2 members */
struct ex3 e3 = {1, 2};
e1.flex[0] = 3; /* undefined behavior, in my case */
e3.flex[0] = 2; /* undefined behavior again */
e2.flex[0] = e3.flex[0]; /* undefined behavior */
代わりに、 malloc
、 calloc
、 realloc
いずれかを使用して構造体を余分なストレージに割り当て、後で解放することもできます。これにより、柔軟な配列メンバーを自由に使用できます。
/* valid: allocate an object of structure type `ex1` along with an array of 2 ints */
struct ex1 *pe1 = malloc(sizeof(*pe1) + 2 * sizeof(pe1->flex[0]));
/* valid: allocate an object of structure type ex2 along with an array of 4 ints */
struct ex2 *pe2 = malloc(sizeof(struct ex2) + sizeof(int[4]));
/* valid: allocate 5 structure type ex3 objects along with an array of 3 ints per object */
struct ex3 *pe3 = malloc(5 * (sizeof(*pe3) + sizeof(int[3])));
pe1->flex[0] = 3; /* valid */
pe3[0]->flex[0] = pe1->flex[0]; /* valid */
'struct hack'は、
フレキシブルアレイメンバーは、C99以前は存在せず、エラーとして扱われていました。一般的な回避策は、 'struct hack'と呼ばれる長さ1の配列を宣言することです。
struct ex1
{
size_t foo;
int flex[1];
};
しかし、これは本当のフレキシブルな配列メンバとは異なり、構造体のサイズに影響します:
/* Prints "8,4,16" on my machine, signifying that there are 4 bytes of padding. */
printf("%d,%d,%d\n", (int)sizeof(size_t), (int)sizeof(int[1]), (int)sizeof(struct ex1));
使用するにはflex
フレキシブルな配列メンバーとしてメンバーを、あなたがそれを割り当てたいmalloc
ことを除いて、上記のようにsizeof(*pe1)
または同等sizeof(struct ex1)
)に置き換えられるoffsetof(struct ex1, flex)
sizeof(*pe1)-sizeof(pe1->flex)
いずれかであるかどうかをsizeof(*pe1)-sizeof(pe1->flex)
。あるいは、希望の長さが0より大きいと仮定して、構造体サイズに既に含まれているので、 "柔軟な"配列の目的の長さから1を引くことができます。他の使用例にも同じロジックを適用できます。
互換性
フレキシブルな配列メンバーをサポートしていないコンパイラとの互換性が必要な場合は、以下のFLEXMEMB_SIZE
ようなマクロを使用することができます:
#if __STDC_VERSION__ < 199901L
#define FLEXMEMB_SIZE 1
#else
#define FLEXMEMB_SIZE /* nothing */
#endif
struct ex1
{
size_t foo;
int flex[FLEXMEMB_SIZE];
};
オブジェクトを割り当てるときには、構造体のサイズ(フレキシブル配列メンバを除くoffsetof(struct ex1, flex)
を参照するにはoffsetof(struct ex1, flex)
フォームを使用する必要があります。これは、フレキシブルな配列メンバをサポートするコンパイラと、ない:
struct ex1 *pe10 = malloc(offsetof(struct ex1, flex) + n * sizeof(pe10->flex[0]));
代替方法は、プリプロセッサを使用して、指定された長さから1を条件付きで減算することです。この形式の不一致と一般的な人為的エラーの可能性が高まったため、私はロジックを別の機能に移しました。
struct ex1 *ex1_alloc(size_t n)
{
struct ex1 tmp;
#if __STDC_VERSION__ < 199901L
if (n != 0)
n--;
#endif
return malloc(sizeof(tmp) + n * sizeof(tmp.flex[0]));
}
...
/* allocate an ex1 object with "flex" array of length 3 */
struct ex1 *pe1 = ex1_alloc(3);
構造体を関数に渡す
Cでは、すべての引数はstructを含む値によって関数に渡されます。小さな構造体の場合、これはポインタを介してデータにアクセスすることによるオーバヘッドがないことを意味するので、良いことです。しかし、プログラマが引数を参照渡しする他の言語に慣れている場合は、巨大な構造体を誤って渡してパフォーマンスが低下することも非常に簡単になります。
struct coordinates
{
int x;
int y;
int z;
};
// Passing and returning a small struct by value, very fast
struct coordinates move(struct coordinates position, struct coordinates movement)
{
position.x += movement.x;
position.y += movement.y;
position.z += movement.z;
return position;
}
// A very big struct
struct lotsOfData
{
int param1;
char param2[80000];
};
// Passing and returning a large struct by value, very slow!
// Given the large size of the struct this could even cause stack overflow
struct lotsOfData doubleParam1(struct lotsOfData value)
{
value.param1 *= 2;
return value;
}
// Passing the large struct by pointer instead, fairly fast
void doubleParam1ByPtr(struct lotsOfData *value)
{
value->param1 *= 2;
}
構造体を使用したオブジェクトベースのプログラミング
構造体は、オブジェクト指向の方法でコードを実装するために使用できます。構造体はクラスに似ていますが、通常はクラスの一部でもある関数が欠けています。これらを関数ポインタのメンバ変数として追加できます。私たちの座標の例にとどまるには:
/* coordinates.h */
typedef struct coordinate_s
{
/* Pointers to method functions */
void (*setx)(coordinate *this, int x);
void (*sety)(coordinate *this, int y);
void (*print)(coordinate *this);
/* Data */
int x;
int y;
} coordinate;
/* Constructor */
coordinate *coordinate_create(void);
/* Destructor */
void coordinate_destroy(coordinate *this);
そして今、Cファイルを実装しています:
/* coordinates.c */
#include "coordinates.h"
#include <stdio.h>
#include <stdlib.h>
/* Constructor */
coordinate *coordinate_create(void)
{
coordinate *c = malloc(sizeof(*c));
if (c != 0)
{
c->setx = &coordinate_setx;
c->sety = &coordinate_sety;
c->print = &coordinate_print;
c->x = 0;
c->y = 0;
}
return c;
}
/* Destructor */
void coordinate_destroy(coordinate *this)
{
if (this != NULL)
{
free(this);
}
}
/* Methods */
static void coordinate_setx(coordinate *this, int x)
{
if (this != NULL)
{
this->x = x;
}
}
static void coordinate_sety(coordinate *this, int y)
{
if (this != NULL)
{
this->y = y;
}
}
static void coordinate_print(coordinate *this)
{
if (this != NULL)
{
printf("Coordinate: (%i, %i)\n", this->x, this->y);
}
else
{
printf("NULL pointer exception!\n");
}
}
座標クラスの使用例は次のとおりです。
/* main.c */
#include "coordinates.h"
#include <stddef.h>
int main(void)
{
/* Create and initialize pointers to coordinate objects */
coordinate *c1 = coordinate_create();
coordinate *c2 = coordinate_create();
/* Now we can use our objects using our methods and passing the object as parameter */
c1->setx(c1, 1);
c1->sety(c1, 2);
c2->setx(c2, 3);
c2->sety(c2, 4);
c1->print(c1);
c2->print(c2);
/* After using our objects we destroy them using our "destructor" function */
coordinate_destroy(c1);
c1 = NULL;
coordinate_destroy(c2);
c2 = NULL;
return 0;
}