0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-12-11

「K&R プログラミング言語C」の 「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

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 を付けてください。
ライブラリのリンクを忘れずに を参照

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

は、ppstruct point という型の構造体へのポインタであることを表します。*pp は構造体で、(*pp).x(*pp).yはメンバーです。. の優先度は * より高いのでカッコは必要です。
C 演算子の優先順位と結合規則 を参照
構造体へのポインタ 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 エラーが発生したのでコメントアウトしています。

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?