サーチ…


前書き

構造体は、多様な型の関連変数のセットを単一のメモリ単位にグループ化する方法を提供します。構造全体は単一の名前またはポインタで参照できます。構造部材にも個別にアクセスすることができます。構造体は関数に渡され、関数から返されます。それらはキーワードstructを使用して定義されます。

単純なデータ構造

構造体データ型は、関連するデータをパッケージ化し、単一の変数のように動作させるのに便利です。

2つのintメンバーを保持する単純なstructを宣言する:

struct point 
{
    int x;
    int y; 
};

xypoint構造体のメンバ (またはフィールド )と呼ばれます。

構造体の定義と使用:

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構造体

structtypedefを組み合わせることで、コードをより明確にすることができます。例えば:

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);
    }
}

柔軟な配列メンバー

C99

型宣言

少なくとも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 */

代わりに、 malloccallocreallocいずれかを使用して構造体を余分なストレージに割り当て、後で解放することもできます。これにより、柔軟な配列メンバーを自由に使用できます。

/* 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 */
C99

'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;
}


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow