.net column

.NET開発者のためのブログメディア

C言語のポインタとは?基本の使い方や構造体・注意点について解説

2020年04月30日
SE
C言語のポインタとはなんですか?
PM
変数のアドレスを記憶する変数のことを指します。

C言語のポインタとは?

ポインタは初心者の最初の壁

C言語の学習にあたって、初心者が最初につまずきやすい壁と言われるのがポインタという文法です。一度分かってしまえばなんてことはないのですが、人によっては理解が難しいかもしれません。ポインタについてわかりやすく解説しましょう。C言語のポインタを理解する前にまず、コンピュータのメモリの仕組みについて簡単に知っておく必要があります。メモリには数値のデータが保存されていますが、その場所は「アドレス」という値で表されます。アドレス(address)とは日本語で「住所」という意味なので、データのある場所を示すのがアドレス、と考えれば理解できるでしょう。

アドレスを示すのがポインタ

頭の中で1,2,3…とそれぞれに番号が振られているロッカーを思い浮かべてください。アドレスはその番号のことなのです。そしてポインタとは、その番号を示すための印(しるし)だとご理解ください。ロッカーの中には数値のデータが入っていて、ポインタという印を使い、割り振られた番号を指定すれば、中身を取り出せるということです。C言語のプログラムで説明しましょう。以下のコードでは、valueは普通の変数です。そしてptr_valがポインタ変数です。ポインタはこのように宣言する時に*をつけます。

int value = 100;
int *ptr_val;

普通の変数に&をつければアドレスを取得できる

そして以下のように普通の変数の先頭に&を付けてポインタ変数に代入すると、普通の変数が入っているメモリのアドレスをポインタに渡すことができます。

ptr_val = &value;

両方の数の中身を画面に表示してみましょう。

printf(“valeの中身は%d\n”,value);
printf(“ptr_valの中身は%d\n”,*ptr_val);

ポインタの中身を取り出す時は、先頭に*をつけます。結果は以下のようになります。

valueの中身は100
ptr_valの中身は100

同じ値になりましたね。ptr_valがvalueと同じ場所のアドレスを指しているからです。

ポインタ変数の中身はアドレス

理解して欲しいのは、valの中身の100をptr_valに代入したのではなく、valのアドレスをptr_valに渡したということです。試しにアドレスも表示してみましょう。

printf(“valeのアドレスは%x\n”,&value);
printf(“ptr_valのアドレスは%x\n”,ptr_val);

結果は以下のようになります。なおアドレスの値は処理系やメモリの状況によってわりますが、どちらも同じ値の16進数が表示されるのはわりません。

valueのアドレスは0x5ff4a3d8
ptr_valの中身は0x5ff4a3d8

配列変数とポインタの関係

次に配列変数のポインタとしての使い方を説明します。

int ary[] = { 100, 200, 300, 400, 500 };

実はこの時点ですでにaryはポインタ数なのです。以下のようにすればそれがわかります。

printf(“ary[0]の中身は%d\n”,ary[0]);
printf(“*aryの中身は%d\n”,*ary);

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

ary[0]の中身は100
*aryの中身は100

配列宣言をした時点で、ポインタを宣言した事と同じ意味を持つのです。

ポインタによる配列変数へのアクセス

ポインタ変数は値を加減して操作することができます。ただし配列変数に対し直接それを行うのは危険なのでやってはいけません。以下のように配列とは別のポインタを用意します。

int ary[] = { 100, 200, 300, 400, 500 };
int *ary_ptr;

ary_ptr = ary; // またはary_ptr = &ary[0];でも可
ary_ptr++;

printf(“*ary_ptrの中身は%d\n”,*ary_ptr);

こうすると、結果は以下になります。ポインタを加算してずらすことができたということですね。

*aryの中身は200

ポインタによる文字列へのアクセス

C言語では文字列はchar型の配列という扱いなので、以下のようにポインタでも扱うことができます。

char str1[] = “”文字列No.1″”;
char *str2 = “”文字列No.2″”;

printf(“”%s\n%s\n””,str1,str2);

結果は以下のように表示されます。

文字列No.1
文字列No.2

数値の配列変数の応用で文字列も同じように扱うことができます。

ポインタによる構造体へのアクセス

ポインタは構造体にも以下のようにしてアクセスできます。

typedef struct {
int age;
char *name;
} PERSON;

PERSON st_per;
PERSON *ptr_st_per;

ptr_st_per = &st_per;
ptr_st_per->age = 18;
ptr_st_per->name = “”太郎””;

printf(“”年齢 %d 名前 %s\n””,ptr_st_per->age,ptr_st_per->name);

「->」という新しい文法が登場しましたが、これはアロー演算子と言います。構造体にはこのようにアロー演算子でアクセスするのが一般的です。

C言語のポインタの応用と落とし穴

ポインタの実践的な使い方

ここまで読んできて「ポインタって何の役に立つの?」と思う人もいるでしょう。ところがポインタはC言語のプログラムには欠かせないのです。例えば以下のような場合に必要になります。

void func(void) {
int a = 100,b = 200, *c;
add(a, b, c);
printf(“”結果 %d\n””,*c);
}

void add(int val1, int val2, int *val3) {
*val3 = val1 + val2;
}

結果 300

このようにポインタで他の関数に変数のアドレスを渡して、別の関数の中で渡した変数の値を操作することができるのです。ポインタがないとプログラムの自由度が大幅に下がりますね。

ポインタの落とし穴「バッファオーバーラン」

便利なポインタですが、アドレスを操作するために危険性もあります。ポインタのよくあるバグの一つに以下のような「バッファオーバーラン」があります。

char buffer[] = { 0, 1, 2, 3, 4, 5 }; // 最後に-1を入れ忘れている
char *p_buf;

p_buf = buffer;
while(*pbuf!=-1) {
printf(“”%d\n””,*pbuf++);
}

このプログラムは-1が来るまで画面にbufferの中身を表示しますが、このようにデータの末尾に-1が無いと終わることができず無限ループとなり、システムが暴走してしまいます。

バッファオーバーランの回避方法

上のプログラムの場合、例えばこのようにサイズチェックを入れればループが止まらないということはなくなります。

char buffer[] = { 0, 1, 2, 3, 4, 5 }; // 最後に-1を入れ忘れている
char *p_buf;
int size = sizeof(buffer);

p_buf = buffer;
for(int i = 0; i < size; i++) {
if (*pbuf == -1) break;
printf(“”%d\n””,*pbuf++);
}

C言語を使う職場では、この例のようにプログラムしなさいというコーディングルールがあるのが普通ですね。

ポインタの落とし穴「ローカル変数を戻り値にする」

もう一つポインタに絡むバグを紹介します。以下のようなプログラムを実装してはなりません。

void func(void) {
printf(“”%s\n””,get_str());
}

*char get_str() {
char *str = “”abc””;
return str;
}

これを実行しても問題なく動作する事もあると思います。何が悪いのでしょうか。それはget_str関数のローカル変数strの有効範囲はget_str内だけなので、他の関数に処理が移ると内容が保証されないのです。そのためデータが破壊されている可能性があります。

ローカル変数を戻り値にしない

上のようなことを行うなら、以下のようにしましょう。

void func(void) {
char buffer[4];
get_str(buffer, sizeof(buffer));
printf(“”%s\n””,buffer);
}

void get_str(char *buf, int size) {
if (size < 4) return;
*buf++ = ‘a’;
*buf++ = ‘b’;
*buf++ = ‘c’;
*buf = ‘\0’;
}

関数を呼ぶ側で入れ物の変数を用意して、ポインタ渡しをすれば良いということです。

ポインタはC言語の醍醐味

ここまで読んできて、「ポインタって怖い、C言語って難しい」と思った人もいるかもしれません。確かにC言語はBASICやJavaやC#などと違い、メモリに直接アクセスするのでより注意を要する言語だと言えます。しかしC言語はその分だけ処理能力が高速のため、組み込み系やゲーム開発ではC言語やC++言語が主流になっています。ポインタを使いこなせばC言語を極めたものと同然と言えるので、是非マスターしてくださいね。

SE
C言語って難しいですが、マスターするととても便利な言語ということですね。
PM
そうですね。ぜひ、C言語を学んでいきましょう!

.NET分野でのキャリアアップをお考えの方は、現在募集中の求人情報をご覧ください。

求人一覧

また、直接のエントリーも受け付けております。

エントリー(応募フォーム)