K&R プログラミング言語C～構造体の基本

「K&R プログラミング言語Ｃ」の　「6.1～6.2 構造体の基本と関数」でばらばらに紹介されていた関数をまとめて、main 関数で具体的な数値を与えてコンパイル、実行してみました。
まず、平面上の座標 ( x, y ) を持つ構造体で点を表し、これを入れ子にして二つの対角点 ( pt1, pt2 ) を持つ構造体で長方形を表します。
```
/* 点: point(x, y) 座標 */
struct point {
    int x;
    int y;
};

/* 長方形: rect 対角点の座標～構造体の入れ子 */
struct rect {
    struct point pt1;
    struct point pt2;
};
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549839/999d25d7-9d29-e8e7-5298-327daac5b213.png)

```
struct point makepoint(int, int)
```
は、引数の二つの整数から点の座標を持つ構造体を作る関数です。
```
/* makepoint: x および y 座標から点を構成する */
struct point makepoint(int x, int y)
{
    struct point temp;
    temp.x = x;
    temp.y = y;
    return temp;
}
```
### 1. メンバーアクセス演算子 `.`
構造体のメンバーアクセス演算子には, `.` と `->` がありますが、まず 
```
構造体名.メンバー名
```
を使って、二つの座標の中点 ( `middle` )、差 ( `diff` )、距離 ( `dist` )、和 ( `sum` ) を求めてみます。
```
struct rect screen;
struct point middle, sum, diff; //中点、和、差の座標

/* 長方形の中点(小数点以下切り捨て) */
middle = makepoint((screen.pt1.x + screen.pt2.x) / 2,
                   (screen.pt1.y + screen.pt2.y) / 2);

/* 長方形の座標間の距離 */
double dist, sqrt(double);
diff = subpoint(screen.pt1, screen.pt2);
dist = sqrt((double)(diff.x * diff.x + diff.y * diff.y));

/* 長方形の座標の和 */
sum = addpoint(screen.pt1, screen.pt2);

/* addpoint: 二つの点を加える */
struct point addpoint(struct point p1, struct point p2)
{
    p1.x += p2.x;
    p1.y += p2.y;
    return p1;
}

/* subpoint: 二つの点を引き算する */
struct point subpoint(struct point p1, struct point p2)
{
    p1.x -= p2.x;
    p1.y -= p2.y;
    return p1;
}
```
ある座標 p が長方形の中にあるかないかを調べる関数
```
int ptinrect(struct point, struct rect)
```
を使うための前処理として、
```
struct rect canonrect(struct rect)
```
によって、与えられた長方形の対角点の `pt1` の各座標が、`pt2` の各座標より小さいという標準形式に変換します。
その際に二つの数値を比較して、小さいほうの数値をとる関数 `min(a, b)` と大きいほうの数値をとる関数　`max(a, b)` をマクロで作成しています。
```
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))

struct rect screen;
screen = canonrect(screen);

/* 座標p(5, 4) は長方形の中にあるか? */
struct point p = {5, 4};
if (ptinrect(p, screen))
    printf("座標p(%d, %d)は長方形の中にあります\n", p.x, p.y);
else
    printf("座標p(%d, %d)は長方形の中にはありません\n", p.x, p.y);
}

/* ptinrect: p が r の中なら 1、そうでなければ 0 を返す */
int ptinrect(struct point p, struct rect r)
{
    return p.x >= r.pt1.x && p.x < r.pt2.x
        && p.y >= r.pt1.y && p.y < r.pt2.y;
}

/* canonrect; 長方形の座標を標準形式にする */
struct rect canonrect(struct rect r)
{
    struct rect temp;
    temp.pt1.x = min(r.pt1.x, r.pt2.x);
    temp.pt1.y = min(r.pt1.y, r.pt2.y);
    temp.pt2.x = max(r.pt1.x, r.pt2.x);
    temp.pt2.y = max(r.pt1.y, r.pt2.y);
    return temp;
}
```
下記のソースコード ( points.c ) をコンパイルするときには
```
$ gcc -Wall -o points points.c -lm
```
のように末尾に `-lm` を付けてください。
※ [ライブラリのリンクを忘れずに](https://qiita.com/percipere/items/678a8ec1299a7564f6a3) を参照
```c
/* points.c */
#include <stdio.h>
#include <math.h>

#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))

struct point {
    int x;
    int y;
};

/* 構造体の入れ子 */
struct rect {
    struct point pt1;
    struct point pt2;
};

struct point makepoint(int, int);
struct point addpoint(struct point, struct point);
struct point subpoint(struct point, struct point);
int ptinrect(struct point, struct rect);
struct rect canonrect(struct rect);

int main(void)
{
    struct rect screen; // 2つの座標を持つ長方形
    struct point middle, sum, diff; //中点、和、差の座標

    /* 長方形の座標 */
    screen.pt1 = makepoint(9, 6);
    screen.pt2 = makepoint(1, 2);
    printf("pt1の座標 = (%d, %d)\npt2の座標 = (%d, %d)\n\n",
            screen.pt1.x, screen.pt1.y, screen.pt2.x, screen.pt2.y);

    /* 長方形の中点(小数点以下切り捨て) */
    middle = makepoint((screen.pt1.x + screen.pt2.x) / 2,
                       (screen.pt1.y + screen.pt2.y) / 2);
    printf("pt1とpt2の中点の座標 = (%d, %d)\n\n", middle.x, middle.y);

    /* 長方形の座標間の距離 */
    double dist, sqrt(double);
    diff = subpoint(screen.pt1, screen.pt2);
    dist = sqrt((double)(diff.x * diff.x + diff.y * diff.y));
    printf("pt1とpt2の距離 = %.3f\n\n", dist);

    /* 長方形の座標の和 */
    sum = addpoint(screen.pt1, screen.pt2);
    printf("pt1とpt2の和 = (%d, %d)\n\n", sum.x, sum.y);

    /* 長方形の標準化 */
    screen = canonrect(screen);
    printf("新しいpt1の座標 = (%d, %d)\n新しいpt2の座標 = (%d, %d)\n\n",
            screen.pt1.x, screen.pt1.y, screen.pt2.x, screen.pt2.y);

    /* 座標p(10, 10) は長方形の中にあるか? */
    struct point p = {5, 4};
    if (ptinrect(p, screen))
        printf("座標p(%d, %d)は長方形の中にあります\n", p.x, p.y);
    else
        printf("座標p(%d, %d)は長方形の中にはありません\n", p.x, p.y);
}

/* makepoint: x および y 座標から点を構成する */
struct point makepoint(int x, int y)
{
    struct point temp;

    temp.x = x;
    temp.y = y;
    return temp;
}

/* addpoint: 二つの点を加える */
struct point addpoint(struct point p1, struct point p2)
{
    p1.x += p2.x;
    p1.y += p2.y;
    return p1;
}

/* subpoint: 二つの点を引き算する */
struct point subpoint(struct point p1, struct point p2)
{
    p1.x -= p2.x;
    p1.y -= p2.y;
    return p1;
}

/* ptinrect: p が r の中なら 1、そうでなければ 0 を返す */
int ptinrect(struct point p, struct rect r)
{
    return p.x >= r.pt1.x && p.x < r.pt2.x
        && p.y >= r.pt1.y && p.y < r.pt2.y;
}

/* canonrect; 長方形の座標を標準形式にする */
struct rect canonrect(struct rect r)
{
    struct rect temp;

    temp.pt1.x = min(r.pt1.x, r.pt2.x);
    temp.pt1.y = min(r.pt1.y, r.pt2.y);
    temp.pt2.x = max(r.pt1.x, r.pt2.x);
    temp.pt2.y = max(r.pt1.y, r.pt2.y);
    return temp;
}
```
- 実行結果
```
$ ./points
pt1の座標 = (9, 6)
pt2の座標 = (1, 2)

pt1とpt2の中点の座標 = (5, 4)

pt1とpt2の距離 = 8.944

pt1とpt2の和 = (10, 8)

新しいpt1の座標 = (1, 2)
新しいpt2の座標 = (9, 6)

座標p(5, 4)は長方形の中にあります
```
### 2. メンバーアクセス演算子 `->`
```
struct point *pp
```
は、`pp` が `struct point` という型の構造体へのポインタであることを表します。`*pp` は構造体で、`(*pp).x` と `(*pp).y`はメンバーです。`.` の優先度は `*` より高いのでカッコは必要です。
※ [C 演算子の優先順位と結合規則](https://learn.microsoft.com/ja-jp/cpp/c-language/precedence-and-order-of-evaluation?view=msvc-170) を参照
構造体へのポインタ `pp` に対しては、メンバーにアクセスする便法として、次の記法が用意されています。
```
pp-> 構造体のメンバー
```
例えば次のように使われ、( 1 ) と ( 2 ) は同じ結果を出力します。
```
struct point origin = {10, 5}, *pp;
pp = &origin;
printf("origin is (%d, %d)\n", (*pp).x, (*pp).y); // (1)
printf("origin is (%d, %d)\n", pp->x, pp->y); // (2)
```
`.` と `->` は左から右へと結合するので、
```
struct rect r = {{3, 4}, {5, 6}}, *rp;
rp = &r;
```
とすると、次の四つの式は等価で、いずれも `3` を出力します。( 下の実行結果参照 )
```
r.pt1.x
rp->pt1.x
(r.pt1).x
(rp->pt1).x
```
最後に、
```
struct {
    int len;
    char *str;
} *p;
```
という宣言が与えられた場合に、以下の記法の意味を本文から抜粋しました。
```
++p->len    ... p ではなく len を 1 増やす
(++p)->len  ... len をアクセスする前に p が 1 増やされる
(p++)->len  ... len をアクセスした後に p が 1 増やされる
*p->str     ... str が指すものを指示する
*p->str++   ... 指すものが何であれ、アクセスした後に str を 1 増やす
(*p->str)++ ... 1 増やされるのは str の指すもの
*p++-str    ... str の指すものをアクセスした後に、p が 1 増やされる
```
それぞれの場合について printf 文で出力してみました。`(*p->str)++` を出力する printf 文を実行すると、segmentation fault エラーが発生したのでコメントアウトしています。
```c
/* strpnt.c */
#include <stdio.h>

struct point {
    int x;
    int y;
};

struct rect {
    struct point pt1;
    struct point pt2;
};

int main(void)
{
    struct point origin = {10, 5}, *pp;
    pp = &origin;

    printf("origin is (%d, %d)\n", (*pp).x, (*pp).y);
    printf("origin is (%d, %d)\n", pp->x, pp->y);

    struct rect r = {{3, 4}, {5, 6}}, *rp;
    rp = &r;
    printf("r.pt1.x = %d rp->pt1.x = %d (r.pt1).x = %d (rp->pt1).x = %d\n",
            r.pt1.x, rp->pt1.x, (r.pt1).x, rp->pt1.x);

    struct {
        int len;
        char *str;
    } *p;

    struct {
        int len;
        char *str;
    } data[] = {{10, "abc"}, {20, "def"}, {30, "ghi"}, {40, "jkl"}};

    p = &data;  // 36 行目
                // 37 行目
    printf("++p->len = %d\n", ++p->len); // 38行目
    printf("(++p)->len = %d\n", (++p)->len);
    printf("(p++)->len = %d\n", (p++)->len);

    printf("*p->str = %c\n", *p->str);
    printf("*p->str++ = %c\n", *p->str++);
//    printf("(*p->str)++ = %c\n", (*p->str)++);
    printf("*p++->str = %c\n", *p++->str);

    return 0;
}
```
実行結果は以下の通りです。
```
$ ./strpnt
origin is (10, 5)
origin is (10, 5)
r.pt1.x = 3 rp->pt1.x = 3 (r.pt1).x = 3 (rp->pt1).x = 3

++p->len = 11    ...(1)
(++p)->len = 20  ...(2)
(p++)->len = 20  ...(3)

*p->str = g      ...(4)
*p->str++ = g    ...(5)
*p++->str = h    ...(6)
```
gdb ( デバッガ ) を使って ( 1 ) ～ ( 6 ) の実行結果を確認してみます。gcc コマンドに -g オプションを付けてコンパイルし、実行ファイル内にシンボルテーブルを保存します。
```
$ gcc -g -Wall -o strpnt strpnt.c
```
gdb を起動して、`p = &data;` の後 ( 38 行目 ) にブレークポイントを設定して、プログラムを実行させます。
```
$ gdb strpnt
 ....
(gdb) b 38
Breakpoint 1 at 0x127f: file strpnt.c, line 38.
(gdb) r
Starting program: /home/ .... /strpnt
```
プログラムが停止した後に p コマンドで data を表示させます。data 配列には四つの構造体 *p が格納されています。
```
Breakpoint 1, main () at strpnt.c:38
38          printf("++p->len = %d\n", ++p->len);
(gdb) p data
$1 = {{len = 10, str = 0x55555555605e "abc"}, {len = 20, str = 0x555555556062 "def"}, {len = 30, str = 0x555555556066 "ghi"}, {len = 40, str = 0x55555555606a "jkl"}}
```
p コマンドで `++p->len` を表示させます ( 1 )。最初 ( 0 番目 ) の構造体の len ( 10 ) を 1 増やして　11 が出力されます。
```
(gdb) p ++p->len
$2 = 11
```
p コマンドで、`(++p)->len` を表示させます ( 2 )。p が 1 増やされて、次 ( 1 番目 ) の構造体の len ( 20 ) が出力されます。
```
(gdb) p (++p)->len
$3 = 20
```
p コマンドで、`(p++)->len` を表示させます ( 3 )。len ( 20 ) が出力された後に、p が 1 増やされます。
```
(gdb) p (p++)->len
$4 = 20
```
p コマンドで、現在の `p->len` を表示させます。次 ( 2 番目 ) の構造体の len ( 30 ) が出力されます。
```
(gdb) p p->len
$5 = 30
```
p コマンドで、現在の `*p->str` を表示させます ( 4 )。2 番目の構造体の str が指す `g` ( "ghi" の 0 番目 ) が出力されます。
```
(gdb) p *p->str
$6 = 103 'g'
```
p コマンドで、`*p->str++` を表示させます ( 5 )。現在の str が指す `g` が出力され、str が 1 増やされます。
```
(gdb) p *p->str++
$7 = 103 'g'
```
p コマンドで、現在の `*p->str` を表示させます。現在の str が指す `h` ( "ghi" の 1 番目 ) が出力されます。
```
(gdb) p *p->str
$8 = 104 'h'
```
p コマンドで、コメントアウトしていた `(*p->str)++` を表示させます。現在の str が指す `h` ( "ghi" の 1 番目 ) が出力された後、`h` ( 104 ) が 1 増やされます。
```
(gdb) p (*p->str)++
$9 = 104 'h'
```
p コマンドで、現在の `*p->str` を表示させます。現在の str が指す 105 ( = 104 + 1 ) に相当する文字 `i` が出力されます。
```
(gdb) p *p->str
$10 = 105 'i'
```
p コマンドで、`*p++->str` を表示させます。現在の str が指す `i` が出力された後、p が 1 増やされます。( 6 ) の結果とは異なります。
```
(gdb) p *p++->str
$11 = 105 'i'
```
p コマンドで、現在の `*p->str` を表示させます。最後 ( 3 番目 ) の構造体の str が指す `j` ( "jkl" の 0 番目 ) が出力されます。
```
(gdb) p *p->str
$12 = 106 'j'
```
gdb を終了します。
```
(gdb) q
```