Edited at

ポインタと配列を組み合わせて遊ぶ

More than 1 year has passed since last update.

Cはコードを書いてないとすぐに感覚が鈍るので、リハビリとして、ポインタと配列をいろいろ組み合わせて遊んでみました。


配列の先頭要素へのポインタ


  • 形式:T *ident

  • 型分類:ポインタ型

  • 被参照型:T

ポインタの使用用途ナンバーワンではないでしょうか。

const char *message = "Hello world!";

printf("%s\n", message);

/*
== 結果 ==
Hello world!
*/


ポインタへのポインタ


  • 形式:T **ident

  • 型分類:ポインタ型

  • 被参照型:ポインタ型

関数に、指定の記憶領域に値(ポインタ)を入れてもらう時に使用します。

void f(const char **ptr_to_ptrval)

{
*ptr_to_ptrval = "pointer to pointer";
}

int main(void)
{
const char *p;
/* 指定の記憶領域に中身(ポインタ値)を入れてもらう。 */
f(&p);

printf("%s\n", p);

return 0;
}

/*
== 結果 ==
pointer to pointer
*/

また、「ポインタ配列の先頭要素へのポインタ」という使い方もあります。argvでお世話になってます。

int i; 

const char *ptr_list[] = {"New Game", "Load", "CG Gallery", "Exit", NULL};
const char **argv2 = ptr_list;

printf("argv2 == %p\n", (void *)argv2);

for(i=0; argv2[i] != NULL; i++)
printf("[%d] %p -> %s\n", i, argv2[i], argv2[i]);

/*
== 結果 ==
argv2 == 0xbfd30444
[0] 0x8048530 -> New Game
[1] 0x8048539 -> Load
[2] 0x804853e -> CG Gallery
[3] 0x8048548 -> Exit
*/


配列の配列


  • 形式:T ident[][]

  • 型分類:配列型

  • 要素型:配列型

配列を要素として持つ配列です。C言語の入門書などで大人気。

「多次元配列」と言うとしばしば怒られるアレですが、公式が「6.3.2.1 配列の添字付け」で「多次元配列」って言っちゃってるんですよね・・・。

int i;

const char menu[][10] = {"New Game", "Load", "CG Gallery", "Exit", ""};

for(i = 0; menu[i][0] != '\0'; ++i)
printf("[%d] %s\n", i, menu[i]);

/*
== 結果 ==
[0] New Game
[1] Load
[2] CG Gallery
[3] Exit
*/

上のサンプルコードでは配列の最終要素に番兵として空文字列""を置いていますが、空文字列であっても末尾にナル文字\0が処理系によって付加されることは規格で保証されているのか気になったのですが、規格によると文字列は「二重引用符で囲まれた0個以上の多バイト文字の列」(「6.1.4 文字列リテラル」)と定義され、また、翻訳フェーズ7において文字列リテラルに値0のバイトが付加される、とのことなので大丈夫でした。


ポインタ配列


  • 形式:T *ident[]

  • 型分類:配列型

  • 要素型:ポインタ型

「配列の配列」と肩を並べるほどの人気ぶり。

複数の配列に対して、それぞれの先頭要素へのポインタを要素として持つ配列です。

const char *menu[] = {"New Game", "Load", "CG Gallery", "Exit", NULL};

int i;

for(i = 0; menu[i] != NULL; ++i)
printf("[%d] %p -> %s\n", i, menu[i], menu[i]);

/*
== 結果 ==
[0] 0x8048510 -> New Game
[1] 0x8048519 -> Load
[2] 0x804851e -> CG Gallery
[3] 0x8048528 -> Exit
*/


配列へのポインタ


  • 形式:T (*ident)[]

  • 型分類:ポインタ型

  • 被参照型:配列型

他人と同じことをしたくない天邪鬼にはこれ。

間接演算子*がオペランドに配列へのポインタをとる式は、結果が配列オブジェクトになります。で、式中の配列オブジェクトを評価した結果は「配列の先頭要素へのポインタ」になります(アドレス演算子&sizeof演算子のオペランドに指定された場合や、char型配列を初期化する初期化子の文字列リテラルを除く)。

char (*message)[13] = &"Hello world!";

printf("%s\n", *message);


配列へのポインタの配列


  • 形式:T (*ident[])[]

  • 型分類:配列型

  • 要素型:配列型を指すポインタ型

天邪鬼というか嫌がらせ。「配列を指すポインタ」の配列。

int i;

char (*menu[])[10] = {&"New Game ",
&"Load ",
&"CG Gallery",
&"Exit ",
NULL};

for(i=0; menu[i] != NULL; ++i)
printf("%p : %s\n", (void *)menu[i], *menu[i]);
/*
== 結果 ==
0x8048510 : New Game
0x804851a : Load
0x8048524 : CG Gallery
0x804852e : Exit
*/

文字列リテラルに半角スペースのパディングをつけて長さを統一していますが、これは、nバイトの文字列リテラルの型はchar [n+1]であるため、menu[]の要素型である「char[10]を指すポインタ型」と型を適合させるためです。非常に使いづらい…。


ポインタへのポインタの配列


  • 形式:T **ident[]

  • 型分類:配列型

  • 要素型:ポインタ型を指すポインタ型

「「各配列の先頭要素へのポインタを要素とする配列」の先頭要素へのポインタを要素とする配列」です(日本語でおk)。

int i, j;

/* 各文字列の先頭要素へのポインタを要素とする配列 */
const char *ptr_list1[] = {"New Game", "Load", "CG Gallery", "Config", "Exit", NULL};
const char *ptr_list2[] = {"Quick Load", "Load", "Exit", NULL};

/* …の先頭要素へのポインタを要素とする配列 */
const char **menu[3];
menu[0] = ptr_list1;
menu[1] = ptr_list2;
menu[2] = NULL;

for(i=0; menu[i]!=NULL; ++i){
for(j=0; menu[i][j]!=NULL; ++j){
printf("* %s ", menu[i][j]);
}
printf("\n");
}

/*
== 結果 ==
* New Game * Load * CG Gallery * Config * Exit
* Quick Load * Load * Exit
*/


関数ポインタの配列


  • 形式:T (*ident[])(parameter-list)

  • 型分類:配列型

  • 要素型:ポインタ型(parameter-listを引数にとり、型Tを返す関数型へのポインタ)

identは配列型オブジェクトを指し示す識別子であり、ポインタが指す関数名ではないことに注意(関数名は出てこない)。

#include <stdio.h>


int f(const char *p)
{
return printf("%s\n", p);
}

int main(void)
{
int i;
int (*fp[])(const char *) = {f, f, f, NULL};

for(i=0; fp[i] != NULL; ++i)
fp[i]("Hello World!");

return 0;
}
/*
== 結果 ==
Hello World!
Hello World!
Hello World!
*/