voidへのポインタ

ポインタとアドレスの違い。そして、(void *)型とは

ポインタという言葉は、一般に『情報を指し示すもの』というように特定の情報があることを、 前提にして使います。
アドレスという場合、単に位置を表している場合が多く、アドレスの所に存在する情報は、 特定していない場合が多いようです。

つまり、アドレスという位置だけでは、 その位置からどれだけの範囲にある何の情報かを表現していない訳です。

対して、ポインタは、アドレスという位置だけでなく、 そこに存在している情報の範囲や種類までを含む用語と言えます。例えば
int * p = (int *)buf;  で、
pは、単なるアドレスでなく、その位置にあるint型記憶域を指し示していると言う訳です。

さてvoid *型は、上記説明のアドレスに相当する型です。
つまり、情報の位置は指し示せるが、何の情報かまでは示すことができない型です。
よって、単純に「指し示す記憶域を表現する」時に使う * のポインタ演算子や []の配列演算子は使えません。 エラーになります。
char buf[256];
void * vp = buf;
*vp = 'A'; /* コンパイルエラー */

ここで、上記の2行目で、コンパイルエラーがないことに着目ください。bufが(char *)型で、 異なる型のポインタ変数に、キャストなしで代入できています。
つまり任意のポインタ型を、void* の変数へ、キャスト無しに直接代入表記できる訳です。
そして、(void *)型は、後で特定のポインタ型に変換して使うことが前提の型ということになります。

(void *)型は、キャストして使う

前述で示したように、(void *)型は、位置は指せるが、その情報を示さないと説明しました。
つまり、使う場合は、何かの型に置き換えて(キャストして)、使うことになります。

以下で、(void *)型を使う例を示しています。
my_malloc関数は記憶サイズを引数にして、buf配列内の未使用位置(char *型)を返す関数です。 その戻り値(void *)型変数pに記憶しています。 そして、(void *)型変数pを介して、さまざまデータ型を記憶する例です。

まず、構造体のサイズの記憶域を取得して、それにデータを記憶しています。

struct xy_t { double x; double y; char a[4];}; /* ←全サイズが20byteと仮定 */
char buf[1024];
int ipos = 0;
char * char_malloc(int size)
{
  char *cp = buf + ipos; /* 未使用の位置を戻り値用変数へ記憶 */
  ipos += size; /* 次に使う未使用位置を更新 */
  return cp;
}
main()
{
  void * p = char_malloc( sizeof(struct xy_t) );
  void * p2;
/* 上記で、buf配列先頭を構造体サイズで利用するため、そのアドレスを取得しています */
  ( (struct xy_t *)p )->x = 5.0;
  ( (struct xy_t *)p )->y = 10.0;
  strcpy( ( (struct xy_t *)p )->a, "XYZ");
      /* 以上で、取得した記憶域にデータを記憶しています。
        ( )で、->演算より、キャスト演算を先行させています。 */

  p2 = char_malloc( ) ;

  p = char_malloc( sizeof( int ) );
   ;
/* p2が指し示す0byte分の記憶域を利用するプログラムなど、以下省略 */
}



問題作成ボタンをクリックして、それに答えてください。
 (なお、問題のイメージはint型が2byteのコンパイラの場合です)

以上のように、記憶域に使えるメモリのアドレスさえ確定できれば、 その記憶域を希望の型で管理できるわけです。