C言語のアドレスとは何なのか?【初心者向け解説】
- 作成日: 2022-01-19
- 更新日: 2024-03-24
- カテゴリ: C言語
C言語のアドレスとは何なのか?
C言語のアドレスとは、変数などのメモリ上の番地のことをいいます。
これは数字の羅列になっていて、この番地にアクセスするとメモリ上のデータを参照することができます。
アドレスはC言語のポインタを扱う時に頻繁に参照します。
「変数のアドレスをポインタ変数に保存・・・」
「アドレスにあるデータにアクセス・・・」
などです。
C言語のアドレスについて理解を深めるのはポインタについて理解を深めるのと同じことです。
この記事ではC言語のアドレスについて詳しく解説します。
関連記事
C言語のポインタをC言語歴17年が解説
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()で解放する
関連記事
C言語の動的配列のリサイズ方法
C言語の静的なメモリと動的なメモリ
アドレス同士の比較
ポインタ変数に保存したメモリのアドレスは比較することができます。
#include <stdio.h>
int main(void) {
int a = 1;
int *p = &a;
int *q = &a;
if (p == q) {
puts("同じメモリです。");
}
return 0;
}
上記のコードをコンパイルして実行すると↓の結果になります。
同じメモリです。
ポインタ変数p
とq
に保存しているアドレスは共に変数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言語のアドレスについて解説しました。
アドレスとポインタは切っても切れない関係であると言えます。
ポインタを使いこなしたい場合はアドレスの理解も深めておきましょう。
🦝 < ポインタとアドレスはマブダチ
🐭 < 切っても切れない間柄