教育・資格支援

【C言語入門】ポインタの仕組みを図とコード例でやさしく解説

20年間エンジニアとして働いた後、フリーランスを経て現在は1人社長として活動しているユウイチです。

プログラミング講師やIT教育を中心に発信しながら、趣味でゲーム開発やシナリオ作成にも挑戦しています。

「創造と教育を通じて、自分らしく生きたい人の“自由な一歩”を支援する」――そんな想いを込めて、このブログを書いています。

今回はC言語の「ポインタ」について書こうと思います。

C言語の「ポインタ」は初心者にとって難しく感じる概念のひとつですが、一度理解すればとても便利な機能です。

今回はポインタの基本から、分かりやすい例とコードを交えて解説していきます!

1. ポインタとは?

ポインタ(Pointer)とは、変数の「アドレス(メモリの場所)」を格納する変数のことです。

通常の変数は値を保存しますが、ポインタはその値が格納されているメモリの場所を保存します。

ポインタを使うことで、メモリの直接操作が可能になり、関数間でのデータの受け渡しや、配列の管理が容易になります。

また、ポインタを利用することで、より効率的なプログラムの作成が可能になります。たとえば、大きなデータ構造を関数に渡す際に、ポインタを使えばメモリのコピーを回避できるので、パフォーマンスの向上につながります。

イメージは以下のような感じです。

難しく考える必要はなく、メモリアドレスを保存しているだけと考えておけば大丈夫です!

例えば、以下のコードがあります。

#include <stdio.h>

int main() {
int a = 10; // 変数aを定義
int *p = &a; // aのアドレスをポインタpに格納

printf("変数aの値: %d\n", a);
printf("変数aのアドレス: %p\n", &a);
printf("ポインタpの値(aのアドレス): %p\n", p);
printf("ポインタpが指す値: %d\n", *p);

return 0;
}

このコードの実行結果は以下になります。(アドレスは適当です)

実行結果(例):
変数aの値: 10
変数aのアドレス: 0x00000100
ポインタpの値(aのアドレス): 0x00000100
ポインタpが指す値: 10

 

それでは、結果について説明しますね。まず、このプログラムでは各変数(aとp)は以下のようにメモリに配置されています。

メモリ構造の図解(4バイト刻みとしています):
メモリアドレス変数
0x00000000
0x00000004
・・・
・・・
0x00000100a (値: 10)
0x00000104p (値: 0x00000100)

変数aは通常の変数になりますので、以下のコード

printf("変数aの値: %d\n", a);
printf("変数aのアドレス: %p\n", &a);

にて変数aに格納されている値の「10」と変数aが格納されているアドレスである「0x00000100」が出力されます。

C言語では通常の変数に「&」をつけると、その変数が格納されているポインタを示すことになっています。

そして次のコード

printf("ポインタpの値(aのアドレス): %p\n", p);
printf("ポインタpが指す値: %d\n", *p);

にてポインタ変数pの値「0x7ffda4b7b8ec」とpの番地に格納されている変数aの値「10」が出力されます。

C言語ではポインタ変数に「*」をつけると、その番地に格納されている変数の値を示すことになっています。

つまり、この例では*pは

「pの値(番地:0x00000100)に格納されている変数(今回の例では変数a)の値」ということになり、「10」ということになります。

言葉だけで理解しようとすると難しく感じますが、メモリ構造の図解と照らし合わせて結果を見ていただければ理解しやすいのではないかと思います^^

ユウイチ
ユウイチ
図を見ながら理解しよう

2. ポインタの活用例

それでは、ポインタの活用例を紹介ていきます。

配列の操作(通常のインデックス操作とポインタ操作の比較)

配列の操作にもポインタは有効です。通常の配列インデックスで操作する場合と比較してみましょう。

#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;
    
    printf("通常のインデックス操作:\n");
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    printf("ポインタを使用した操作:\n");
    for (int i = 0; i < 5; i++) {
        printf("*(p + %d) = %d\n", i, *(p + i));
    }
    
    return 0;
}
実行結果(例):
通常のインデックス操作:
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50

ポインタを使用した操作:
*(p + 0) = 10
*(p + 1) = 20
*(p + 2) = 30
*(p + 3) = 40
*(p + 4) = 50

この例では、以下のように配列の値が格納されています(アドレスは適当な値)

メモリ構造の図解:
メモリアドレス変数
0x00000000arr[0](値: 10)
0x00000001
0x00000002
0x00000003
0x00000004arr[1](値: 20)
0x00000005
0x00000006
0x00000007

※arr[2]以降は割愛

int型は4byteですので(8byteの場合もありますが、4バイトとして説明)、格納するのに4バイト分の領域が必要です。

ですので、上記のイメージで格納されます。そして、今回のポイントは以下のコードになります。

ポイント

①:int *p = arr;

②:*(p + i)

まず①のコードですが、int型のポインタ変数pに配列arrの先頭アドレスを入れています。上記の例では0x00000000が入ることになります。

C言語では配列名をインデックスをつけずに記述すると先頭アドレスを示すことになっています。ですので、&arr[0]とも書けます。(どちらで記述しても同じ意味になります。)

そして②のコードですが、ポインタ変数にiを加算しています。ループの中の処理になるので、*(P + 0),*(P + 1),*(P + 2)・・・となっていくのですが、ポインタの加算には注意が必要です。

まず、P + 0ですが、この例ではPが0x00000000ですので、結果はP + 0 = 0x00000000となります。

ですので、*(P + 0) = *(0x00000000) = arr[0]の値 = 10となります。次にP + 1ですが、これは単純に1を加算して

P + 1 = 0x00000001とはなりません。

ここが少し混乱するところなのですが、ポインタ変数はポインタで定義した型のバイト数分加算するというルールになっています。

pはint型のポインタ変数です。

この例ではint型を4バイトとしておりますので、P + 1となると、4バイト進めてP + 1 = 0x00000004となります。

ですので、*(P + 1) = *(0x00000004) = arr[1]の値 = 20

ということになります。ポインタ変数の加算は型のバイト数によって変化するということはしっかりと覚えておいてくださいね。

ポイント

ポインタ変数の加算値は宣言された型によって変わる

文字列操作

文字列の操作にもポインタを使用することができます。以下のコードで説明します。

#include <stdio.h>

int main() {
    char str[] = "Hello, world!";
    char *p = str;
    
    printf("文字列: %s\n", p);
    
    while (*p) {
        printf("%c", *p);
        p++;
    }
    printf("\n");
    
    return 0;
}
実行結果:
文字列: Hello, world!
Hello, world!

この例では、以下のように配列の値が格納されています(アドレスは適当な値)

メモリ構造の図解:
メモリアドレス変数
0x00000000H
0x00000001e
0x00000002l
0x00000003l
0x00000004o
0x00000005,

※「w」以降は割愛

今回のポイントは以下のコードになります。

ポイント


while (*p) {
printf("%c", *p);
p++;
}

p++ですが、これはp+1と同じ意味でインクリメントと言います。今回のポインタ変数pはcharですので1バイトですので、

P + 1 = 0x00000001となります。

その結果、ポインタを1づつ(1バイトづつ)加算していき、printfで順次出力するので結果は文字列出力と同じ「Hello,world!」となります。

判定条件のwhile (*p) ですが、これは文字列配列が終了したかどうか?を確認しています

Cの文字列の終端は '\0' になるのですが、while (*p)p が指す値が '\0' でない限り継続。という意味になります。つまり、文字列の最後まで処理するということです。

while (*p)while (*p != '\0')と同じ意味になるのですが、while (*p)と書く方が多いので、文字列を最後まで判定した場合はwhile (*p)と覚えておいて良いかと思います。

まとめ

今回はポインタの概念と、基本的な使用方法について説明しました。

ポインタには他にも、ダブルポインタ(ポインタのポインタ)や、関数ポインタなどもあるのですが、一気に説明してしまうと混乱してしまうので、今回は基本的な部分にフォーカスしました。

しかし、基本をしっかり理解できれば、応用もさほど難しくありません。

またダブルポインタ(ポインタのポインタ)や、関数ポインタなどについても記事を書きたいと思います。

C言語、頑張っていきましょうね^^

ポイント

ポインタはC言語において非常に重要な概念である

メモリアドレスを直接操作することで柔軟なプログラムが作成可能

より効率的なプログラムが実現可能


✅ ITキャリアを育てるコミュニティ「ITキャリアラボ」

未経験からIT業界に挑戦したい方、資格学習、プログラミング学習が続かない方、転職に不安がある方へ。

僕自身、未経験からエンジニアになり、フリーランス、そして起業へと進んできました。

だからこそ、同じように悩んでいる人たちに寄り添い、サポートしたいという想いで、

LINE公式アカウント「ITキャリアラボ」 を開設しました。

🔹 こんな方におすすめです!

こんな方におすすめ

  • IT業界へ未経験からチャレンジしたい方
  • ITパスポート・基本情報技術者などの資格勉強中の方
  • プログラミング学習でつまずいている方
  • 転職・キャリアチェンジを検討中の方
  • モチベーションを保つために仲間がほしい方
  • 将来的な独立に興味がある方

📌参加は無料・匿名OK・LINEだけで完結!

こちらで詳しく紹介してますので、まずは気軽にのぞいてみてくださいね👇

ITキャリアラボ

ITキャリアを育てるコミュニティ「ITキャリアラボ」|自分らしい働き方を目指す人へ

20年間エンジニアとして働いた後、フリーランスを経て現在は1人社長として活動しているユウイチです。 プログラミング講師やIT教育を中心に発信しながら、趣味でゲーム開発やシナリオ作成にも挑戦しています。 ...

続きを見る

  • この記事を書いた人
  • 最新記事

ユウイチ

20年間ソフトウェアエンジニアとして働いた後、フリーランスを経て現在は1人社長として活動。 プログラミング講師やIT教育を中心に活動しながら、趣味でゲーム開発やシナリオ作成にも挑戦中。どちらも「創ることを通じて人を笑顔にしたい」という想いから始めた、大切なライフワーク。 「創造と教育で、人生に迷う人の“自由な一歩”を支援」を理念に発信中。

-教育・資格支援