今回は、「C言語/変数とアドレスの関係」についての説明です。
1.初めに
前回、以下のような主要なデータ型が存在すると述べました。
データ型種類 | 意味 | メモリ | 範囲 |
---|---|---|---|
unsigned char signed char | 符号無し文字型 符号付き文字型 | 1Byte | 0~255 -128~+127 |
unsigned short int signed short int | 符号無し短整数型 符号付き短整数型 | 2Byte | 0~65535 -32768~+32767 |
unsigned int signed int | 符号無し整数型 符号付き数型 | 4Byte | 0~4294967295 -2147483648~+2147483647 |
unsigned long int signed long int | 符号無し長整数型 符号付き長整数型 | 4Byte | 0~4294967295 -2147483648~+2147483647 |
unsigned long long int signed long long int | 符号無し長長整数型 符号付き長長整数型 | 8Byte | 0~18446744073709551615 -9223372036854775808~+9223372036854775807 |
float | 単精度浮動小数点型 | 4Byte | |
double | 倍精度浮動小数点型 | 8Byte |
これらのデータ型を用いてこれまで変数を使用してきましたが、実は変数には数値の他にアドレスというデータが存在します。
アドレスとは、メモリの中での所在地を示す情報のことです。
今後ポインタというものについて説明するためには理解しておくべき概念なので、今回は変数とアドレスの関係について記述していきます。
2.アドレスとメモリの仕組みについて
まずは、アドレスとは何なのかをもう少し詳しく深堀りしていきます。
パソコンやUSBメモリを買う際、16GBや32GBといったメモリ容量が表示されていますよね?
この16GBや32GBという表記は、1Byteの入れ物が何個存在しているメモリなのかを指しています。
1Byteの入れ物とは、8bitで構成された入れ物のことです。
1bitの各部屋には0か1という情報を入れることができるので、2進数で言うところの255まで表現可能です。
図1の場合は、「01000110」なので、2+4+64=70という数値を表していることになります。
このような1Byteの情報を組み合わせることで、様々なデータ(音声や画像など)が構成されているのです。
仮に1KBの画像があった場合、1KBは1024Byteのことなので、図1のような入れ物を1024個使用することで画像を表現していることになります。
画像はピクセルと呼ばれる小さな点の集まりなので、各ピクセルの色情報を表現した入れ物が用意されていて、それぞれのピクセルを所定の位置に配置することで画像というものが構成されています。
この1024個の1Byteの入れ物のそれぞれの位置情報がアドレスというわけです。
ちなみに、「k/キロ」という単位は通常なら×1000を指していますが、メモリの単位においては×1024になります。
同様に、1MBだったら1000×1000Byteではなく1024×1024Byte、1GBなら1000×1000×1000Byteではなく1024×1024×1024Byteを指していることになります。
何故こんな中途半端に見える1024という数字になっているのかと言うと、1024とは2の10乗のことだからです。
コンピュータは2進数を取り扱うものなので、1000倍に相当する単位が存在しないのです。
だから、1024を仮想的に1000だと述べているわけです。
3.メモリ容量を表示する方法
アドレスの見方に入る前に、各データ型のメモリ容量の確認方法について記述していきます。
先程表示した表の内容を全て覚えているのが理想ではあるのですが、各データ型のメモリ容量を確認する機能も搭載されているんです。
#include<stdio.h>
void main() {
int a = 123;
float b = 12.3f;
double c = 12.3;
char d = ‘a’;
printf(“a = %d。aはintなので%dByteの容量を持ちます。\n”, a, sizeof(int));
printf(“b = %f。bはfloatなので%dByteの容量を持ちます。\n”, b, sizeof(float));
printf(“c = %f。cはdoubleなので%dByteの容量を持ちます。\n”, c, sizeof(double));
printf(“d = %c。dはcharなので%dByteの容量を持ちます。\n”, d, sizeof(char));
}
4~7行目でint・float・doube・char型の変数を定義・初期化しています。
floatは本ブログで初めて登場しましたが、5行目のように末尾に“f”を付けるルールになっています。
なので、bは普通に12.3となっています。
肝心のメモリ容量の確認は、8~11行目で登場するsizeofが実行してくれます。
【sizeof()】の括弧内にデータ型を入力すると、そのデータ型のメモリの大きさが何Byteなのか、値を出してくれます。
このプログラムを実行すると、以下のようになります。
最初に載せたデータ型とメモリ容量などの対応表通りになっているのが確認できましたね。
ただ、intに関しては4Byteと表示されていますが、使用しているコンパイラによって変化してくるので4Byteになるとは限りませんので注意してください。
ちなみに、このプログラムを実行すると警告が出てきます。
なんかいっぱい警告が出ていますが、これらは無視して構いません。
変数とメモリ容量・アドレスを確認するためにprintfをちょっと変な使い方をしているので警告が出ているだけで、エラーが発生しているわけではありませんので…。
4.アドレスの表示方法
では、変数に対応したアドレスを実際に表示してみましょう。
変数の定義・初期化までは先程のプログラムの使い回しです。
#include<stdio.h>
void main() {
int a = 123;
float b = 12.3f;
double c = 12.3;
char d = ‘a’;
printf(“a = %d。aのアドレスは0x%xです。\n”, a, &a);
printf(“b = %f。bのアドレスは0x%xです。\n”, b, &b);
printf(“c = %f。cのアドレスは0x%xです。\n”, c, &c);
printf(“d = %c。dのアドレスは0x%xです。\n”, d, &d);
}
各変数の前に“&”がくっついていますね。
この“&”を付けることで変数のアドレスを割り当てて表示することができます。
ただ、アドレスは16進数で表示されますので、printfであらかじめ「0x」と表示させています。
このプログラムを実行すると、以下のようになります。
これがアドレスというものです。
aという変数のデータは「c1effa04」というアドレスに格納されているわけです。
ここで、もう一度プログラムを実行してみます。
すると、表示されたアドレスが図6と別物になっているのがわかります。
先程しれっと述べたのですが、“&”を付けるとアドレスが“割り当てられます”。
アドレスと言われると住所をイメージするので勘違いしやすいのですが、プログラムにおけるアドレスは一定の場所を表しているわけではないのです。
何故そんなことになっているかと言うと理由は色々あるのですが、わかりやすいのは「使用してはいけないメモリ領域があるから」です。
例えば、図7では「0x59affa34」というメモリ内のアドレスに変数a(123)というデータが保存されているわけですが、もし「0x59affa34」というアドレスに別のプログラムやアプリのデータが先に保存されていたらどうなると思いますか?
データを上書きしてしまうんですよね。
そうなると、上書きにより消されてしまったデータを参照していた別のプログラムやアプリが正常に動作しなくなってしまいます。
値が書き換えられてしまったわけですからね。
なので、“&”でアドレスを取得する場合は、必ず使用されていない領域から自動で割り当てられるようになっています。
だから、実行するごとにアドレスが変わるのです。
プログラムを実行している最中のメモリ内の一時的な置き場(アドレス)を指定しているだけなので、プログラムを一回終えてもう一回実行した際にアドレスが変わったところで影響は無いんです。
ちなみに、“&”はプログラミングにおいては“アンパサンド”と読みます。
普通に“アンド”と呼んでいることもありますけどね。
以上、「C言語/変数とアドレスの関係」についての説明でした。