C言語 スキルアップ

C言語初心者がつまずくポインタをわかりやすく解説!理解すれば怖くない!

人生を自由に楽しむために会社員を辞め、フリーランスを経て法人化し、現在は一人社長をしています。ユウイチです。

今回は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言語において非常に重要な概念である

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

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

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

ユウイチ

20代前半から20年間エンジニアとしてキャリアを積みフリーランスへ転身。 2年間フリーランスとして活動後、起業し1人社長となる。 ソフトウェア開発とプログラミング講師をメインに、ゲームクリエイター・シナリオライターとしても活動中。

-C言語, スキルアップ