MENU
おすすめプログラミングスクール紹介用バナー
Webpia編集部
Webpiaはプログラミングとノーコードについて紹介するWebメディアです。主に10~30代向けに記事を執筆しております。

【C言語】ポインタを理解しよう!わかりやすくメリットを解説します!

当サイトはページの一部でPR活動を実施し、得られた収益で運営されています。広告費用や収益性を考慮したランキング付けは、一切行なっておりません。詳細は、サイトポリシーWebpiaでコンテンツが出来上がるまでを参照ください。

こんにちは。今井(@ima_maru)です。

C言語を学ぶ上で最初につまづきやすいランキング上位である『ポインタ』

私の周りのC言語を学んでいる人たちは「難しい」「分からない」と言っている人が多かったように感じます。

今回はC言語を始めたての方に向ける記事で、C言語におけるポインタという概念やメリットなどをわかりすく、C言語のサンプルコードを用いて解説していきます。

あわせて読みたい
給料をもらいながらプログラミングを学んでエンジニアになる! プログラミングを学ぶためのお金と時間がない...スクールには通わずに、お金をもらいながらプログラミングを学びたい... このような悩みを持った方が記事をご覧になって...
もくじ

C言語のポインタを理解しよう!

ポインタ (pointer) とは、あるオブジェクトがなんらかの論理的位置情報でアクセスできるとき、それを参照する(指し示す)ものです。

簡単に言えば、何かを指し示すものというイメージです。

パソコンのディスプレイ、もしくはスマホの画面を指さしてみてください。

その人差し指がポインタということになります。

イメージはそんな感じです。

今回はC言語の「特定のメモリ領域を表現する」ポインタを軸に話を進めていきます。

C言語のポインタ変数の基礎

ポインタC言語の特徴的な機能のひとつです。

ここでは、どのような機能なのかということと使い方をご紹介します。

C言語のポインタにかかわる記号

C言語において、&(アンパサンド)*(アスタリスク)という記号があります。

ここでは、以下の関係が成り立ちます。

&変数名 = その変数のアドレス
*ポインタ変数の変数名 = 「ポインタ変数がさすアドレス」の値

サンプルコードを用意しましたので、コピーしていろいろいじってみてください。

#include <stdio.h>

int main(void) 
{
	int A;
	int* B;

	A = 3;
	B = &A;

	printf("Aのアドレス=%p Aの値=%d\n", &A, A);
	printf("Bのアドレス=%p Bの値=%p Bの中身=%d\n", &B, B, *B);

	return 0;
}

ちなみに実行結果はこうなります。

Aのアドレス=0093FBDC Aの値=3
Bのアドレス=0093FBD0 Bの値=0093FBDC Bの中身=3

もし下のような変数とポインタ変数があるならば、

アドレス変数名
0x0061FF2CA7
0x0061FF28B0x0061FF2C
ポインタ変数Bが変数Aのメモリアドレスを保存している
  • &A=0x0061FF2C
  • &B=0x0061FF28
  • *B=7 (0x0061FF2Cすなわち変数Aの値)こいつが一番大事
  • *A「*」はポインタ変数にしかつかないため)

という関係が成り立ちます。

ポインタ変数の使い方

パスワードという意味でPassという変数を作りましょう。

int Pass=1234;
アドレス変数名
0x0061FF2CPass1234

変数Passの値とアドレスを表示してみましょう。その際、以下の関係を利用しましょう。

&Pass=0x0061FF2C

printf("値%d アドレス%p\n", Pass, &Pass);

実行結果はこのようになります。

値1234 アドレス0x0061FF2C

次にポインタ変数を作ります。このポインタ変数は特殊でルールがあります。

ポインタ変数にはアドレスを入れる

このルールを忘れないでください。

なぜかというと、ポインタ変数とはそもそも、参照したい変数をアドレスを使って呼び出すというのが目的なので、変数自体には参照したい変数のアドレスを入れる必要があるためです。

では、変数Passを参照するポインタ変数Pointerを作成します。

値には変数Passのアドレス0x0061FF2Cすなわち&Passを入れておきます。

int *Pointer;
Pointer = &Pass;
アドレス変数名
0x0061FF2CPass1234
0x0061FF28Pointer&Pass(0x0061FF2C)
ポインタ変数Pointerが変数Passのメモリアドレスを保存している

このような関係になっています。

ここはよく理解してほしい重要なところです。

最後に、PointerからPassの値を参照してみましょう。

printf("%d\n",*Pointer);

ポインタ変数PointerがPassのアドレスを指示していますので、*PointerでPassの値を参照することができます。

1234

実行結果は1234、すなわちPassの値が出てきます。

使い方をサラッと紹介したところで値交換のサンプルコードを紹介します。

ポインタを使ったswap関数

#include <stdio.h>

void swap(int *x, int *y);

int main(void)
{
	int a=5,b=12;
	printf("a=%d,b=%d\n",a,b);
	
	swap(&a,&b);
	
	printf("a=%d,b=%d\n",a,b);

	return 0;
}

void swap(int *x, int *y)
{
	int tmp;
	tmp=*x;
	*x=*y;
	*y=tmp;
}

簡単に言えば値を交換するプログラムを関数化したものですね。

実行結果は以下のようになります。

a=5,b=12
a=12,b=5

しっかりと値が交換されていますね。

ポインタ変数x,yにそれぞれa,bのアドレスを持ってきていますので、交換前はこういう関係になっています。

アドレス変数名
0x0061FF00a5
0x0061FF04b12
アドレス変数名
0x0061FF08(関数内のポインタ変数)x0x0061FF00
0x0061FF0C(関数内のポインタ変数)y0x0061FF04
xはaのアドレスを、yはbのアドレスを保存している。

これより、*x=5,*y=12として値の参照ができます。

ポインタの話ではないですが、tmpとはtemporaryの略で一時的な値の保管として使っています。

C言語でポインタを使うメリット

簡単に言ってしまえば、ポインタとは何かを位置で示すものです。

位置情報です。

では、位置情報を使うことでどのようなメリットがあるのでしょうか。

メモリの節約

ポインタの最大のメリットはこの「メモリの節約になる」ということです。

例えば、以下のようにdouble型配列の中身を10000個で生成したとき、データサイズはどのくらいになるでしょうか?

#include <stdio.h>

// 配列の中身の個数
#define DATASIZE 10000

int main(void)
{
	// 容量の大きな配列を定義
	double Data_1[DATASIZE];

	// データのメモリ容量を表示
	int size = sizeof Data_1;
	printf("データサイズ : %dbyte\n", size);

	return 0;
}

double型は一つで8byteを使います。

さらにそれが10000個あるとすれば、8×10000=80000byte使うことになります。

それを踏まえたうえでこのプログラムをみてください。

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

// 配列の中身の個数
#define DATASIZE 10000

int main(void)
{
	// 容量の大きな配列を定義
	double Data[DATASIZE];

	// 各値を乱数で生成
	for (int i = 0; i < DATASIZE; i++) {
		Data[i] = (double)rand() / rand();
	}

	// 表示する配列を格納する配列を用意
	double CopyData[DATASIZE];

	// 各値をコピーデータにコピー
	for (int i = 0; i < DATASIZE; i++) {
		CopyData[i] = Data[i];
	}

	// データを出力
	for (int i = 0; i < DATASIZE; i++) {
		printf("SumpleData[%d]  \t: %4.4lf\n", i, CopyData[i]);
	}
    
	return 0;
}

このプログラムでは、先ほどと同じ容量の配列を二つ用意して値はランダムで生成しています。

この際に行っているデータのコピーですが、見てわかる通り、一つ一つ値をコピーしています。

これを表にするとこうなります。

アドレス変数名データサイズ
0x009EC2ECCopyData[0]2.13048byte
0x009EC2ECCopyData[1]0.98088byte
0x009EC2ECCopyData[2]4.61478byte
0x009EC2ECCopyData[3]0.43648byte
配列CopyDataの各要素はdouble型のデータのコピーなので8byteずつ計80000byteを占有している

このように、配列の各要素はdouble型のデータのコピーです。

すべて8byteずつで10000個、合計80000byte占有しているということです。

つまり、Dataという配列と丸々おんなじ配列を作っているということになります。

何が言いたいかというと、

80000byteのデータをもう一つ作っていること自体がメモリの無駄遣いだ!

と言いたかったのです。

じゃあどうするか?

その答えが「ポインタ」です。

どのようにメモリを節約するか、見てみたほうが理解が早いでしょう。

表示のところで、ちょっとポインタの特殊な使い方をしています。

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

// 配列の中身の個数
#define DATASIZE 10000

int main(void)
{
	// 容量の大きな配列を定義
	double Data[DATASIZE];

	// 各値を乱数で生成
	for (int i = 0; i < DATASIZE; i++) {
		Data[i] = (double)rand() / rand();
	}

	// 表示する配列のアドレスを格納するポインタを用意
	double* pData;

	// DataのアドレスをpDataにコピー
	pData = Data;

	// データを出力
	for (int i = 0; i < DATASIZE; i++) {
		printf("SumpleData[%d]  \t: %4.4lf\n", i, *(pData + i));
	}

	return 0;
}

このように書くとデータの値ではなくデータのメモリアドレスを参照するポインタで表現することができます。

アドレス変数名データサイズ
0x0074C588pData0x0074C5A0(&Data[0])4byte
ポインタ変数pDataは配列の先頭番地のアドレスを保存しているだけなので実質4byteのみを占有しています

実際にポインタを使った例も使わなかった例も実行結果はこのようになります。

SumpleData[0]  	: 2.1304
SumpleData[1]  	: 0.9808
SumpleData[2]  	: 4.6147
...
SumpleData[9997]  	: 2.7206
SumpleData[9998]  	: 1.1182
SumpleData[9999]  	: 1.625

では本当にポインタによってメモリの消費が抑えられているのでしょうか?

定数定義したDATASIZEの値を大きくしてみた結果、

ポインタを使わなかった例では、DATASIZE=64000程でコンパイルの際にエラーが出ました。

一方ポインタを使用した例では、DATASIZE=128000程までエラーが出ませんでした。

2倍ですね。これが何を意味するかというのはお気づきでしょう。

コピーを作っている分メモリ容量が2倍に跳ね上がっているんです。

これによりわかることは、

ポインタを使うことでコピーを作らずに位置情報だけでデータを参照することができる

ということです。これがポインタの一番大きなメリットです。

おわかりいただけたでしょうか。

ポインタのメリット
  • メモリ節約
  • 処理速度向上
  • ほかにもいろいろ...

最後に

ポインタというのは非常に使い勝手がいいと同時に、エラーの原因によくなりうる厄介なものでもあります。

ですので、紛らわしくてこんがらがると思いますが、ポインタとアドレスの関係だったりこんがらがりそうなところから突き詰めてみて、完全にマスターしてみてください。

最後に、省メモリ化について、不動産屋のたとえ話をします。

ポインタを使わないで、値をコピーするのは、同じデータをもう一個作ることです。

不動産屋でいえば、お客様のために物件をもう一個作るのと同じです。

逆に、ポインタを使う場合は、位置情報を保存するだけなのでデータの複製は起こりません。

不動産屋でいえば、物件の住所のみを記録しているのと同じです。

家が二つあるか一つだけかの違いです。

プログラミングの世界では、物件を複製するのか、住所を記録してアクセスするのか、この使い分けが必要になります。

物件を複製した場合は、片方の家に変更を加えても、もう片方には何の影響もありませんね。

一方住所の場合、家は一つだけなのですから、すべての変更が反映されますね。

ポインタというのは、このように同じものを指し示す道具となりえるので、関数によって値を変えてもらうときにはポインタ渡しということをしたりするんです。

と、この話はここまでにして、終わりましょう。

以上、「【C言語】ポインタを理解しよう!わかりやすくメリットを解説します!」でした。

最後まで読んでいただきありがとうございます。

よかったらシェアしてね!
もくじ