未だに構造体に関する検索が多いようなので、少し書いてみようと思う。
他のサイトにも解説があるので、初学者の頃、自分がわかりづらかった点について書いてみようと思う。

構造体とは

複数の変数をひとまとめにして扱うようにしたもの。
例えば、座標のように (x,y) をまとめて扱えると便利だろう。

struct 構造体タグ名 {
     型 変数; // メンバ変数と言う
      :
};

のように宣言する。

例1:構造体の場合

// 実際に変数が割り当てられるわけではない。
struct point_st {
    int x;
    int y;
};

int
main(int argc, char *argv[])
{
    struct point_st point;

    point.x = 10; // 構造体のメンバ変数にアクセスする時は、"." を使う。
    point.y = 20;

    return 0;
}

例2:構造体へのポインタの場合

#include <stdlib.h>
struct point_st {
    int x;
    int y;
};

int
main(int argc, char *argv[])
{
    struct point_st *pointp = (struct point_st *)malloc(sizeof(struct point_st));

    // 構造体へのポインタの場合、->(アロー演算子と呼ばれているようだ)でアクセスする。
    pointp->x = 10;
    pointp->y = 20;

    return 0;
}

わかりづらかった点

色々な書式があってわかりづらい。

以下は同じものだ。

パターン1:

struct point_st {
    int x;
    int y;

    return 0;
};

struct point_st point;

パターン2:

typedef struct point_st {
    int x;
    int y;
} point_t;

point_t point;

パターン3 (タグ名省略):

typedef struct {
    int x;
    int y;
} point_t;

struct 〜 と書くのは、タイプ量も多いし、長くて読みづらいので、私は、パターン3をよく使う。

構造体自身へのポインタを含む場合の書き方がわからない。

リスト構造を作る場合に、構造体自身へのポインタを示す場合の書き方。

パターン1:

struct point_st {
    int x;
    int y;
    struct point_st *next;
};

int main(int argc, char *argv[])
{
    struct point_st *point1p;
    struct point_st *point2p;

    point1p = (struct point_st*)malloc(sizeof(struct point_st));
    memset(point1p, 0, sizeof(struct point_st));
    point1p->x = 1;
    point1p->y = 2;
    point1p->next = NULL;
    point2p = (struct point_st*)malloc(sizeof(struct point_st));
    memset(point2p, 0, sizeof(struct point_st));
    point2p->x = 3;
    point2p->y = 4;
    point2p->next = point1p;
    return 0;
}

パターン2:

typedef struct point_st {
    int x;
    int y;
    struct point_st *next;
} point_t;

int main(int argc, char *argv[])
{
    point_t *point1p;
    point_t *point2p;

    point1p = (struct point_st*)malloc(sizeof(struct point_st));
    memset(point1p, 0, sizeof(struct point_st));
    point1p->x = 1;
    point1p->y = 2;
    point1p->next = NULL;
    point2p = (struct point_st*)malloc(sizeof(struct point_st));
    memset(point2p, 0, sizeof(struct point_st));
    point2p->x = 3;
    point2p->y = 4;
    point2p->next = point1p;
    return 0;
}

同様に、読みやすさから、パターン2を使う。

構造体をどのようにして関数に渡すのかわからない

この構造体を他の関数にどのように渡すべきなのか。

#include <stdio.h>

typedef struct {
    double x;
    double y;
} point_t;

void
print_point(point_t point)
{
    printf("%f,%f\n", point.x, point.y);
}

int
main(int argc, char *argv[])
{
    point_t point;

    point.x = 123.4;
    point.y = 456.7;
    print_point(point);
    return 0;
}

としたいかもしれない。(まあ、これでも動く。)

しかし、C 言語の場合は、引数はコピーされて渡される。(値渡し)
構造体は複数の変数を束ねておりサイズが大きく、
コピー時間は、小さなプログラムであれば問題ないが、処理の量が多いと、かなりの時間になる。

そのため、通常はポインタ(=構造体の先頭アドレス)を渡すことが多いと思う。

#include <stdio.h>

typedef struct {
    double x;
    double y;
} point_t;

void
print_point(point_t *pointp)
{
    printf("%f,%f\n", pointp->x, pointp->y);
}

int
main(int argc, char *argv[])
{
    point_t point;

    point.x = 123.4;
    point.y = 456.7;
    print_point(&point);
    return 0;
}

リターン値を構造体にしたい場合はどうするのか

#include <stdio.h>

typedef struct {
    double x;
    double y;
} point_t;

void
print_point(point_t *pointp)
{
    printf("%f,%f\n", pointp->x, pointp->y);
}

point_t
xy_point(double x, double y)
{
    point_t point;
    point.x = x;
    point.y = y;
    return point;
}

int
main(int argc, char *argv[])
{
    point_t point;

    point = xy_point(123.456, 789.123);
    print_point(&point);
    return 0;
}

としたいかもしれない。昔はコンパイルエラーだった。(と思う)
構造体をリターンすることができなかった。
今はコピーされてリターンするようだ。

昔ながらのやりかただと以下のようになる。

#include <stdio.h>

typedef struct {
    double x;
    double y;
} point_t;

void
print_point(point_t *pointp)
{
    printf("%f,%f\n", pointp->x, pointp->y);
}

void
xy_point(double x, double y, point_t *ret)
{
    point_t point;
    ret->x = x;
    ret->y = y;
}

int
main(int argc, char *argv[])
{
    point_t point;

    // point にリターン値を入れてもらうため、構造体へのポインタを渡す
    xy_point(123.456, 789.123, &point);
    print_point(&point);
    return 0;
}

どのようにコンパイルされるか見たことがないので確信が無いが、
構造体自身をリターンできるのであれば、前者の方法でも良いかもしれない。