the ド田舎エンジニア

東京とは名ばかり 八王子市は尾崎 幸一朗(通称:サチロー)による気まぐれブログです

【C言語】汎用ポインタの利便性

サチローこと幸一朗です。

汎用ポインタについて、使い道がわかりませんと、質問を受けたため、自分なりに解説。

【大前提】

通常、変数は型の整合性を取らなければならない。

int i = 128;
char c;

c = i;
printf("%d\n", c);

これの実行結果は「-128」となる(はず)。
charは1バイトの変数なので、「11111111」が最大値となります。
が、最上位のビットは符号ビットとなり、正の値では「01111111」が最大値。
つまり127までしか保持できない。

そこに対して4バイト変数を代入したため、オーバーフローが発生したという例。
※ちなみに、コンパイラによっては警告すら出さない。

代入元の型と、代入先の型は一致しているべきである。

【ポインタの扱い】

ポインタ変数は、変数が確保している領域の先頭アドレスを保持する。

int i = 128;
int *ip = &i;

printf("i[%p]\n", &i);
printf("ip[%p]\n", ip);

表示結果は一致する。

変数が領域を確保することは、ポインタ変数も同様。
では、下記の結果はどうなるか?

typedef struct {
  char foo;
  char bar;
} hoge;

printf("%d\n", sizeof(char));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(hoge));
printf("%d\n", sizeof(char *));
printf("%d\n", sizeof(int *));
printf("%d\n", sizeof(hoge *));

実行環境によって異なるが、
1
4
2
8
8
8
となる。

ポインタ変数では変数の先頭アドレスを格納するため、そこに格納されている値に依存しない。

【汎用ポインタ】

ポインタ変数同士であれば、アドレスを格納する点では共通しているが、問題は中身を参照する場合。

typedef struct {
  char foo;
  char bar;
} hoge;

hoge st;
int *p = &st;

printf("%d\n", p->foo);

この場合、「foo」という子は知りませんと、コンパイルエラーが発生する。


ここでvoid型のポインタの登場。

typedef struct {
  char foo;
  char bar;
} hoge;

hoge st;
void *p = &st;
hoge *pst;

pst = (hoge *)p;

printf("%d\n", pst->foo);

一旦、void型のポインタに退避し、後からキャストすることで中身を参照することができる。

【汎用ポインタの利便性】

例えばこんな構造

typedef struct {
  int key;
  int value;
} token;

const token token_list[] = {
  { 1, 10 },
  { 2, 11 },
  { 3, 12 },
};

token型の配列を宣言。
keyを渡して値を得るhashのような使い方をしたい。

がしかし、この形式で管理できる値はint型に限定される。
void型のポインタにすることにより、これが回避される。

typedef struct {
  int key;
  void *value;
} token;

typedef struct {
  int foo;
  char bar;
} hoge

int i = 10;
char c = 'c';
char str[128 + 1] = "fuga";
hoge hoge_st;

const token token_list[] = {
  { 1, &i },
  { 2, &c },
  { 3, str },
  { 4, &hoge_st },
};

値を取り出したい場合、値を取り出した先で、同じ型のポインタ変数で受ければ良い。

/*
 tokenの最後尾を知る手段として、末端のkeyにそれを示す値が格納されているものとする。
 それが無理ならsizeをfetch関数に渡す。
*/
#define GetValue(type, key, list)    (type *)fetch(key, list)

void *fetch(int key, token *list)
{
  void *ret = NULL;

  while (list->key != KEY_TAIL) {
    if (list->key == key) {
      ret = list->value;
      break;
    }
  }
  return ret;
}      

int *ip;
hoge *hoge_p;

ip = GetValue(int, 1, token_list);
hoge_p = GetValue(hoge , 4, token_list);

といった具合に、型に依存せずにデータを管理できる。


型に依存しないという点では便利ですが、乱用は避けてください。
中身に何が入っているのか訳が分からなくなり、メンテナンス時に苦労します。