2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C言語の変数とかポインタをちょっとだけ理解する

2
Last updated at Posted at 2020-10-06

はじめに

本記事は、いろんなテキストやサイトの説明を見てみたけどいまいちポインタが分からない、という方に向けて書いたつもりです
目標はC言語のポインタが使えるようになることです
なので混乱しがちな変数のメモリには触れず、簡単のためにアドレスと値が対応しているように書いています

ポインタで苦しんでいる方に質問を受けた感じ、主に以下の傾向がありました

  • 値とアドレス、ポインタ変数とアドレスなどが混沌としている
  • 代入が変数にどのように作用するか知らない
  • 関数に引数として変数を渡したときの決まりを知らない

C言語の基本的なルールを抑えられていないためにポインタが理解できていないことが多いようです

ここでは簡単なコードを例に変数の状態の変化を見ていきます
その中でC言語のルールを確認しつつポインタの理解に繋げていきます

int型を使った例

ポインタの前にまずはint型の変数を確認していきましょう
ポインタを理解するにはまずポインタでない普通の変数を理解することが重要です

# include <stdio.h>

int main() {
    int a = 0;          // (1)
    int b = 100;        // (2)
    a = b;              // (3)
    printf("%d\n", a);  // (4)

    return 0;
}

コメント(4)の部分でどんな値が出力されるでしょうか
下の結果を見る前に考えてみてください

実行結果

100

簡単ですね
ではどうしてこのような結果になるか、変数の状態に注目ながら確認していきましょう

変数の状態を確認する

ここでは変数の状態を、変数名, , アドレス, で表現します
この4つの要素を追うことで変数の状態が理解できます

4つもあるなんて大変!と思うかもしれませんが、変数宣言後に変化するのは だけなので安心してください

では上記のコードで変数の状態がどのように変化しているか順に見ていきましょう

コメント(1)での変数の状態

int a = 0 でaが0で初期化されました
このときの変数aの状態は以下のように表せます

変数名 アドレス
a int 0001 0

※アドレスの値は例です。以降の表でも同様

コメント(2)での変数の状態

int b = 100 でbが100で初期化されました
このときの変数の状態は次のようになります

変数名 アドレス
a int 0001 0
b int 1001 100

bは新たに作成されたのでaとはアドレスが異なりますね

コメント(3)での変数の状態

a = b でaにbが代入されました

変数名 アドレス
a int 0001 100
b int 1001 100

aの値が100に更新されましたね
a = b は、aの値をbの値である100で更新する処理です
そのためaのアドレスは変わらず、値のみ100に変化します
aがbの状態とすべて同じになるわけではないんですね

代入は '左辺の値' を更新する処理 であることを覚えておいてください

よって、コメント(4)で出力される値は 100 になります

コードの説明は以上になります
変数名, , アドレス, を使って変数の状態を確認するイメージが掴めたでしょうか

では次からintのポインタ型について見ていきます

intのポインタ型を使った例

# include <stdio.h>

int main() {
    int a = 0;           // (1)
    int* b;              // (2)

    b = &a;              // (3)
    printf("%d\n", *b);  // (4)

    *b = 100;            // (5)
    printf("%d\n", a);   // (6)

    return 0;
}

コメント(4)、(6)の部分でどんな値が出力されるでしょうか
下の結果を見る前に考えてみてください

実行結果

0
100

あっていましたか?
コピペでいいので実際にコードを実行して動作を確認してみてください

ではどうしてこのような結果になるか、変数の状態を見ながら確認して行きましょう

コメント(1)での変数の状態

int a = 0 でaが0で初期化されました

変数名 アドレス
a int 0001 0

コメント(2)での変数の状態

int* b でintのポインタ型の変数bが宣言されました

変数名 アドレス
a int 0001 0
b int* 1001 ????

宣言されたので変数bは作成されますが、初期化されていないので値は不明です
これは int c; と宣言したときに、cの値が分からないのと同じです

コメント(3)での変数の状態

b = &a でbに &a が代入されました

変数名 アドレス
a int 0001 0
b int* 1001 0001

bの値だけがaのアドレスで更新されています

変数の前に & を付けるとその変数のアドレスを表すようになるのでした
つまり &a は変数aのアドレス 0001 です
そして代入は左辺bの値を更新する処理でした

よって、bの値だけがaのアドレス 0001 で更新されます

コメント(4)での変数の状態

特に更新がないので変数の状態は(3)と同じです

変数名 アドレス
a int 0001 0
b int* 1001 0001

printf("%d\n", *b) で何が出力されるでしょうか

ポインタ変数は * を前につけることで、そのポインタ変数が値に持つアドレスの変数になるのでした
つまり、bはaのアドレスを値として持っているので、*b とすることでaと同じ状態になります

表に *b を加えると次のようになります (*bは変数名ではないですが)

変数名 アドレス
a int 0001 0
b int* 1001 0001
*b int 0001 0

a*b は名前が違うだけで同じものなんですね

ほんと!?と思った方は実際に確かめてみましょう
確認のためにaと*bの状態を実際に出力してみます
変数のアドレスは printf("%p", &a) のようにして出力できます

確認用のコード

# include <stdio.h>

int main()
{
    int a = 0;
    int *b;

    b = &a;

    printf("変数名= a, 型=int, アドレス=%p, 値=%d\n", &a, a);
    printf("変数名=*b, 型=int, アドレス=%p, 値=%d\n", &*b, *b);

    return 0;
}

出力結果 (※アドレスの値は実行環境によりことなります)

変数名= a, 型=int, アドレス=61ff18, 値=0
変数名=*b, 型=int, アドレス=61ff18, 値=0

aと*bのアドレスと値が同じになっていますね
今後確かめたい変数がある場合は適宜この方法を使って確認してください

長くなりましたが、以上から(4)の出力は 0 になります

コメント(5)での変数の状態

*b = 100 で*bに100が代入されました

変数名 アドレス
a, *b int 0001 100
b int* 1001 0001

先ほどaと*bは名前が違うだけで同じものだと分かったので一緒に書いています
*bに100が代入されたので*bの値が100になり、同じものであるaの値も100になりました

よって、コメント(6)での出力は 100 になります

int型の変数とintのポインタ型の変数の関係のイメージがなんとなくつかめたでしょうか
次は関数の引数に注目して変数の状態を見ていきます

int型を引数とする関数の例

# include <stdio.h>

void f(int b) {
    printf("%d\n", b);  // (2)
    b = 100;            // (3)
}

int main() {
    int a = 0;          // (1)
    f(a);
    printf("%d\n", a);  // (4)

    return 0;
}

コメント(2),(4)の部分でどんな値が出力されるでしょうか
下の結果を見る前に考えてみてください

実行結果

0
0

あっていましたか?
ではどうしてこのような結果になるか確認していきます

コメント(1)での変数の状態

int a = 0 でaが0で初期化されています

変数名 アドレス
a int 0001 0

コメント(2)での変数の状態

関数fのなかでint型の変数bが新たに作成されます

変数名 アドレス
a int 0001 0
b int 1001 0

aとbのアドレスが異なる部分が重要です。

  1. bは f(a) で渡されたaとは関係なく 新しい変数として作成される ため、aとアドレスが異なります

  2. そして、bの値は f(a) で渡された aの値で初期化される ので、bの値はaと同じ0になります

この2つは引数についてのC言語での決まり事です
ここをしっかり押さえておくことが、関数やポインタを理解する上で重要です
文章だと難しく感じるかもしれませんが、表を見ると分かりやすいと思います

以上より、コメント(2)では 0 が出力されます

コメント(3)での変数の状態

b = 100 でbに100が代入されます

変数名 アドレス
a int 0001 0
b int 1001 100

これはaとは関係のない処理なのでaの値は変化しません

コメント(4)での変数の状態

変数名 アドレス
a int 0001 0

f(a) の処理が終了したので変数bは消滅しました
これは スコープ という言葉で説明される仕組みですが、本筋からそれるのでここでは説明しません

コメント(3)以降に変数aの状態を変更する処理がないため、変数aの状態は(3)の状態と同じになります
よって、コメント(4)では 0 が出力されます

説明は以上になります
関数の引数となっている変数の状態のイメージが掴めたでしょうか
では次に関数の引数がintのポインタ型になった場合を見ていきましょう

intのポインタ型を引数とする関数の例

# include <stdio.h>

void f(int* b) {
    printf("%d\n", *b); // (2)
    *b = 100;           // (3)
}

int main() {
    int a = 0;          // (1)
    f(&a);
    printf("%d\n", a);  // (4)

    return 0;
}

コメント(2), (4)の部分でどんな値が出力されるでしょうか

実行結果

0
100

あっていましたか?
int型の場合と違い変数aの値が(4)の時点で変更されていますね
コピペでいいので実際にコードを実行して動作を確認してみてください

ではこれから変数の状態を確認していきましょう

コメント(1)での変数の状態

int a = 0 でaが0で初期化されています

変数名 アドレス
a int 0001 0

コメント(2)での変数の状態

関数fの中でintのポインタ型の変数bが作成されます

変数名 アドレス
a int 0001 0
b int* 1001 0001

ここからint型の場合と異なっていますね
順を追って見ていきましょう

C言語の決まりで、bはaとは関係なく作成されるのでした
このときのbの状態は以下のようになります

変数名 アドレス
b int* 1001 ????

int* b と宣言した場合と同じですね
初期化されていないので値が不明な状態です

そしてもう一つのC言語の決まりで、bの値は f(&a) で渡された &a つまり 0001 で初期化されるのでした
初期化後のbの状態は以下のようになります

変数名 アドレス
b int* 1001 0001

では、 printf("%d\n", *b) の出力は何になるでしょうか
aと*bを含めた変数の状態は次のようになります

変数名 アドレス
a, *b int 0001 0
b int* 1001 0001

intのポインタ型のbはaのアドレスを値として持つので、aと*bは同じものになるのでした
以上より、コメント(4)では 0 が出力されます

わざわざポインタなんか使わずに直接変数aを使いたいと思いますが、C言語では残念ながらできません
変数aはmain関数で定義されているので、関数fからは直接使えないのです
そのため面倒ですが、関数fの中で変数aの値を変更する場合は、ポインタ型の変数bを使う必要があります

scanfが scanf("%d", &a) のように変数のアドレスを受け取るようになっているのはこのためだったんですね

コメント(3)での変数の状態

*b = 100 で*bに100が代入されました

変数名 アドレス
a, *b int 0001 100
b int* 1001 0001

aと*bは同じものなのでaの値も100になります

コメント(4)での変数の状態

変数名 アドレス
a int 0001 100

f(&a) の処理が終了したので変数bは消滅しました
コメント(3)以降にaを変更する処理がないため、aの状態は(3)の状態と同じになります
よって、コメント(4)では 100 が出力されます

説明は以上です

おわりに

ポインタは変数のアドレスを値として扱うため直感的でなく最初は理解が難しいと思います
さらに関数や配列と一緒になるとより複雑になります
しかしそのような複雑な状況も、ひとつひとつをみればC言語の小さな決まりからできていることが分かります
変数で混乱しないためには、今扱っている変数の状態を把握した上で、C言語の決まりに沿って考えることが重要です

2
2
1

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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?