ポインターの基礎から関数ポインターまで | C言語

プログラムで扱う変数の値は、メモリー上に格納されます。各変数はメモリー上のどこか(アドレス)に配置されていて、変数に値を代入するということはメモリー上の指定されたアドレスに値を格納するということになります。そしてポインター変数とは、変数のアドレスそのものを表す変数となります。

  • 変数は宣言順にメモリーに格納されるとは限りません。
目次

アドレス演算子と間接演算子

ポインターに触れ始めて最初に躓くのが、アドレス演算子「&」と間接演算子「*」です。この2つは慣れるまで混同しがちです。

項目内容
アドレス演算子変数に「&」を付けると、その変数のアドレスを表します。
間接演算子ポインター変数に「*」を付けると、そのポインターの指す先の値を表します。
int a, b;
int *p;  // int型のポインター型変数

a = 3;
p = &a;  // pにaのアドレスが格納される

// bにpの指す先であるaの値が格納される
// すなわち"b = 3"となる
b = *p;
  • 上記例において「b = p;」は認められません。pはアドレスであるためbには代入できないのです。コンパイルエラーが出るため、誤って実装することはありませんが注意してください。

ポインター演算

ポインター変数に対しても加算や減算を行うことができます。するとどうなるか。ポインター変数の型のサイズ分だけアドレスが前後するのです。下記はその例となります。

int a[5] = {1, 2, 3, 4, 5};
int b, c, d;
int *p;

p = a;   // 配列aの先頭アドレス、"&a[0]"と同意
b = *p;  // b = 1
p++;
c = *p;  // c = 2
p += 2;
d = *p;  // d = 4

またポインター変数は配列形式で表記することもできます。こうすることで人がコードを認識し易くなるため、積極的に使用すると良いでしょう。

int a[5] = {1, 2, 3, 4, 5};
int b, c, d;
int *p;

p = a;
b = p[0];  // b = 1
c = p[1];  // c = 2
d = p[3];  // d = 4

ヌルポインター

ヌルポインターは何も指していないことが保証されているポインターで、通常はマクロNULLを使用します。NULLは相手の型に依存しません。キャストは不要です。

  • NULLの定義は処理系依存です。必ずしもゼロ番地であるとは限りません。NULLの代わりに「0」と書いてはいけません。
  • NULLは<stddef.h>、<stdio.h>、<stdlib.h>、<string.h>で定義されています。いずれかをインクルードしてください。

ポインターの基本的な使い方

ポインターの利用方法として、下記のようなものがあります。

  • 関数の引数として配列を渡す。
    意識せず使っている人もいるかもしれませんが、配列を引数とする場合はポインターを使います。
  • 関数から複数の値を受け取る。
    通常関数の戻り値はreturn文で返す1つのみです。しかし引数にポインターを用いることで、複数の値を返すことができます。
void swap(int *a, int *b) {
  int tmp = *a;

  *a = *b;
  *b = tmp;
}

関数ポインター

変数だけでなく、実は関数もメモリー上のどこかに配置されています。そしてC言語では、変数だけでなく関数のアドレスもポインターに格納することができるのです。関数ポインターを使用することで、次のようなメリットがあります。

  • コードがシンプルになり可読性が上がります。
  • 状態の増減に対して、ソースコードの修正が容易になります。
#define CMD_NUMBER (2)

// コマンド応答処理関数の型定義
typedef void (*CMDFUNC)(const char data[], uint8_t len);
typedef struct {
  char   *cmdstr; 
  CMDFUNC cmdFunc; 
} CMD_TABLE;

// コマンドごとの処理
static void cmdRecvSERI(const char data[], uint8_t len);
static void cmdRecvCHAN(const char data[], uint8_t len);

// コマンド関数テーブル
static const CMD_TABLE m_CmdTable[CMD_NUMBER] = {
  {"SERI", cmdRecvSERI},
  {"CHAN", cmdRecvCHAN}
};

static void Check_Command(const char data[], uint8_t len) {
  for (uint8_t i = 0; i < CMD_NUMBER; i++) {
    if (strncmp(data, m_CmdTable[i].cmdstr, 4) == 0) {
      m_CmdTable[i].cmdFunc(data, len);
      break;
    }
  }
}

さらにポインターについて学びたいなら

書籍「新・標準プログラマーズライブラリ C言語 ポインタ完全制覇」をおすすめします。ポインター一本に絞られているためその内容は濃く、また文章も面白いので飽きずに読めます。C言語初心者より、「&」や「*」ぐらいなら使えるよという少し慣れてきたころの人におすすめです。

目次