C言語のアドレスとは何なのか?【初心者向け解説】

393, 2022-01-20

目次

C言語のアドレスとは何なのか?

C言語のアドレスとは、変数などのメモリ上の番地のことをいいます。
これは数字の羅列になっていて、この番地にアクセスするとメモリ上のデータを参照することができます。

アドレスはC言語のポインタを扱う時に頻繁に参照します。

「変数のアドレスをポインタ変数に保存・・・」
「アドレスにあるデータにアクセス・・・」

などです。

C言語のアドレスについて理解を深めるのはポインタについて理解を深めるのと同じことです。
この記事ではC言語のアドレスについて詳しく解説します。

関連記事
C言語でポインタと配列を入れ替える(スワップする)方法
C言語の関数で戻り値にポインタを使う
C言語の関数ポインタを引数に渡す方法
C言語の配列とポインタについて
C言語の文字列を初期化する方法: 文字配列、文字列ポインタの初期化

アドレスを変数から取り出す

変数のアドレスを変数から取り出すには、「&」演算子を使います。
変数の頭に&を付けると、その変数のアドレスを取り出せます。

int a = 1;
int *p = &a;  // 変数aのアドレスを取り出す

取り出したアドレスはポインタ変数に保存することができます。
↑のコードではpというポインタ変数に変数aのアドレスを保存しています。

アドレスを見る

このアドレスの値を実際に見ることは可能です。
printf()の書式に%pを指定します。
そうするとポインタ変数が保存しているアドレスの値を見ることができます。

#include <stdio.h>

int main(void) {
    int a = 1;
    int *p = &a;  // 変数aのアドレスを取り出す

    printf("%p\n", p);  // pの持つアドレスの値を見る
    // 0x12345678

    return 0;
}

このアドレスの値が、実際のメモリの番地になります。
ポインタ変数はこのアドレスを持つことができて、ポインタ変数を操作することでそのアドレス上のデータにアクセスすることができるわけです。

アドレスからデータを参照する

では実際にポインタ変数に保存しているアドレスから、実体となるデータを参照してみましょう。

#include <stdio.h>

int main(void) {
    int a = 1;
    int *p = &a;

    printf("%d\n", *p);  // pの持つアドレスの実体(変数a)を参照する
    // 1

    return 0;
}

ポインタ変数に保存しているアドレスの実体、つまりメモリ上のデータを参照するには、ポインタ変数にアスタリスク(*)を付けます。
こうするとポインタ変数を使ってアドレスの実体を参照できます。
↑のコードではポインタ変数pには変数aのアドレスが保存されています。
*pを実体化すると、aの値を参照できます。

動的メモリのアドレス

stdlib.h」をインクルードすると使えるmalloc()calloc()などの関数。
これらは動的なメモリの確保に使います。
動的なメモリの確保でも、確保したメモリの先頭位置はアドレスで表現されます。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *p = malloc(sizeof(int));  // int型のサイズのメモリを動的に確保する

    printf("%p\n", p);  // malloc()が確保したメモリのアドレスを見る
    // 0x12345678

    free(p);  // 確保したメモリを解放する
    return 0;
}

malloc()で確保したメモリのアドレスはポインタ変数に保存できます。
動的に確保したメモリは解放する必要があります。
解放を忘れるとメモリリークと言うバグになります。

この解放処理はfree()関数で行われます。
free()アドレスの値を頼りにメモリを解放します
そのため確保したメモリのアドレスはきちんと管理しておかないといけません。

仮にアドレスの値がデタラメに書き変わってしまったり、つまり番地がデタラメになったら、free()関数で確保したメモリを解放できなくなってしまいます。
つまり動的なメモリ確保では以下のことに気をつけなければいけません。

  • malloc()で確保したメモリのアドレスはちゃんと管理しておく

  • 確保したメモリはfree()で解放する

アドレス同士の比較

ポインタ変数に保存したメモリのアドレスは比較することができます。

#include <stdio.h>

int main(void) {
    int a = 1;
    int *p = &a;
    int *q = &a;

    if (p == q) {
        puts("同じメモリです。");
    }

    return 0;
}

上記のコードをコンパイルして実行すると↓の結果になります。

同じメモリです。

ポインタ変数pqに保存しているアドレスは共に変数aのアドレスです。
p == q」でアドレスの値を比較することで条件が真になり、puts()が実行されます。

このようにメモリ上のアドレスを比較すると、その変数のユニーク性を比較することができます。
メモリに確保されるデータにはすべてユニークな番地が振られていますので、その番地であるアドレスを比較すれば、どのデータがどのデータかわかるということですね。

その変数が何者なのか確認したい時はアドレスを比較するようにしましょう。

ポインタ変数のアドレス(ポインタのポインタ)

変数にはアドレスがあり&で取り出せますが、ポインタ変数にもアドレスがあります
これも&で取り出すことができます。

#include <stdio.h>

int main(void) {
    int a = 1;
    int *p = &a;
    int **pp = &p;

    printf("p = %p\n", p);
    printf("pp = %p\n", pp);
    printf("*pp = %p\n", *pp);
    printf("**pp = %d\n", **pp);

    return 0;
}

上記のコードをコンパイルして実行すると↓のような結果になります。
(環境によってアドレスの値は変わります)

p = 0x7ffde3af4584
pp = 0x7ffde3af4578
*pp = 0x7ffde3af4584
**pp = 1

ポインタ変数pのアドレスはppに保存されています。
p*ppのアドレス値が同じであることに注意してください。
*ppの指すアドレスは変数aのアドレスです。
**ppで変数aの値を取り出すことができます。

このようなポインタ変数のアドレスを保存しているポインタ変数を「ポインタのポインタ」と言ったりします。
ポインタのポインタは、たとえば関数にポインタ変数のアドレスを渡して、ポインタの持つアドレスを関数内で変更したい時などに使われます。

「ポインタのポインタのポインタ」はあまり使いませんが「ポインタのポインタ」は比較的に良く使われます。

おわりに

今回はC言語のアドレスについて解説しました。
アドレスとポインタは切っても切れない関係であると言えます。
ポインタを使いこなしたい場合はアドレスの理解も深めておきましょう。

(^ _ ^)

ポインタとアドレスはマブダチ

(・ v ・)

切っても切れない間柄

この記事のアンケートを送信する