恐怖のマニュアル

どぶろく
記事: 75
登録日時: 12年前

恐怖のマニュアル

投稿記事 by どぶろく » 11年前

その昔、Quick C のマニュアルにこういうものが載っていました。

char *(*(*var)())[10];
int (*var(int (*)[3]))[3];
int **var[2][3];
int *(*var[2])[3];

何の意図があってこういうものを載せていたのかが分からない。
Quick C のマニュアルをぶん投げた(頭にきて捨てた)のでなおさら分からない。
とにかく何の説明もなく、こういうのも出来るという紹介みたいな感じでした。
偶然にもメモしていたのでお目にかけることが出来ました。
暇な人はどういう宣言なのか解いてみてください。
最後に編集したユーザー どぶろく on 2013年9月09日(月) 17:31 [ 編集 1 回目 ]

どぶろく
記事: 75
登録日時: 12年前

Re: 恐怖のマニュアル

投稿記事 by どぶろく » 11年前

どうやらヒマな人がいないようなので、ヒマ人である私が挑戦します。まずは、

char *(*(*var)())[10];

からですが、その前に我流の解釈になりますので元になる理屈を述べさせて下さい。

■ 優先順位による違い(複合宣言子の解釈)
-----------------------------------------
宣言時の (), [], * の解釈は、実行時の解釈とは違いますので、 なかなか難しいもの
があります。無意識のうちに理解しているようでも、複合宣言子になると手が付けられ
なくなります。また、実行時の動作も理解しがたくなってきます。

() と、[] は同じ優先順位を持ちますので、左から右に解釈していきます。 * は上の
2つより優先順位が低いので、後から解釈されます。そして最後に型指定子を適用しま
す。デフォルトの解釈順位を変更するには、() を使います。

これらで宣言された識別子(名前)を宣言子と呼び、複数の [],(),* で宣言された識
別子を複合宣言子と呼びます。

特に注意したいのは () が関数を表しているのか、解釈順位を変更しているのかがわか
らなくなってくることです。自分で使う分には混乱しないのがC言語の面白いところで
すが、時間がたてば自分が書いた複合宣言子も理解できなくなってきます。

C/C++の宣言を読むときは「優先順位の逆読み法=優先順位の低い方から読む」か、
「記述順序の逆読み法=右から左に読んでいく」か、「内読み法=最も内側の括弧から
外に向かって読む」か、「コンパイラの解釈方法を調べるか(これが内読み法かもしれ
ない)」か、カッコをじっくり眺めるか、なんとなくなれるかです。次の2つは基本で
すので覚えしょう。

char *a[6];

なら、* と [] では、[]の方が優先順位が高いので、逆に優先順位が低い方から読むと
「ポインタの配列」となります。()を付ければ、*(a[4])となり、ますます訳がわかり
ませんね~。やはり、なれるしかない。

char (*a)[6];

なら、()によって優先順位が変更されたので *aから解釈して、aはポインタであり、a
が指すのは配列である。となります。つまり a は「配列へのポインタ」になります。
a を +1 すると、6バイト先のアドレスを指します。つまり、a は何を指すポインタか
と言えば、2次元配列の2次元目を指すポインタであると理解しておいた方がいいよう
です。つまり、char (*a)[6]; は、char a[][6]; という感じです。

どぶろく
記事: 75
登録日時: 12年前

Re: 恐怖のマニュアル

投稿記事 by どぶろく » 11年前

● 続・複合宣言子の解釈(内読み方への挑戦)
--------------------------------------------
宣言時の (), [], * の呼び方と解釈の方法

() 関数修飾子
[] 配列修飾子
* ポインタ修飾子

これらを複数使用して宣言された識別子を複合宣言子と呼びます(前に言ったような気
がする)。その解釈方法に内読み法を使えば・・・。

1、識別子から始めて、その右側へと [] または () を探していきます。
2、これらの [] や () の中を解析し、次に左側の * を探します。
3、どの段階においても右カッコ ) を見つけたときは、先に進んでいく前に、1と2
の手順を踏みます。
4、最後に型指定子を解釈します。

char *(*(*var)())[10]; の場合、

もっとも内側の括弧の、(*var)() を解釈すれば、verは、ポインタ変数であり、() を
指す。()は関数修飾子なので関数を表し、verは関数へのポインタであるとなります。

var // 識別子 var は、たぶん変数。
*var // varは、ポインタ変数。
(*var) // varは、* より優先順位の高い []か、()へのポインタと予想。
(*var)() // verは、関数へのポインタ。

つぎの括弧 (*(*var)()) を見ると、関数へのポインタに * を付けたものなので、var
の指す関数はポインタである。つまり、varが指すのは「ポインタを返す関数」となり
ます。

*(*var)()

verは「ポインタを返す関数」へのポインタ。
つぎに右側に [10] があるので、関数が返すポインタは、「10個の要素を持つ配列」を
指す。となります。

(*(*var)())[10]

verは 「ポインタを返す関数へのポインタ」であり、その関数の返すポインタは 10個の
要素を持つ配列を指す
つぎにこれの左側に * があるので、10個の要素を持つ配列はポインタである。となります。

*(*(*var)())[10]

verは 「ポインタを返す関数へのポインタ」であり、その関数の返すポインタは 10個の
要素を持つ配列を指す。 その配列はポインタである。

最後に型指定子を解釈して、その配列であるポインタは char型を指す。となります。

char *(*(*var)())[10]

verは「ポインタを返す関数へのポインタ」であり、その関数の返すポインタは 10個の
要素を持つ配列を指す。その配列はポインタである。ポインタである配列が指すのは
char型である。ということになります。

次の2つのことを覚えておいてください。

● 配列は、その要素として関数を持つことは出来ません。
● 関数は、配列および関数を返すことは出来ません。

これらを解決するには、

◆ 配列に関数を入れるときは、配列を「関数へのポインタ配列」として宣言し、関数
のアドレスを配列に入れます。

int (*pf[3])();      // pf[] は、関数のアドレスを格納する配列
pf[0]=int型の関数名1;   // 関数名は関数のアドレス(関数ポインタ)を表す
pf[1]=&int型の関数名2;  // & は、付けても付けなくても良い
pf[2]=int型の関数名3;
(*pf[0])();         // ポインタ配列 fp[0] が指す関数を実行する

◆ 「配列および関数」を返す関数を作るときは、「配列のポインタおよび関数のポイ
ンタ」を返す関数にします。

typedef int (*PHH)[5];  // PHH は「2次元配列を指すポインタ」の型指定子
typedef int (*PFUNC)();  // PFUNC は「関数を指すポインタ」の型指定子

int *func(void){};   // ポインタを返す関数
PHH func(void){};  //「2次元配列のポインタ」を返す関数
PFUNC func(void){};  //「関数のポインタ」を返す関数

どぶろく
記事: 75
登録日時: 12年前

Re: 恐怖のマニュアル

投稿記事 by どぶろく » 11年前

それでは、まず最初につまずくであろう

char (*(*var)())[10];

からやりましょう。そのあとに、

char *(*(*var)())[10];

をやります。

CODE:

#include 

typedef char (*PHH)[5];  //「配列を指すポインタ」の型指定子を作成

//--------------------------------------------------------------------
//「配列を指すポインタ」を返す関数
//--------------------------------------------------------------------
PHH test1(void)
{
    static char  ahh[3][5]={{"ABCD"},{"abcd"}};  

    return ahh;
}
//--------------------------------------------------------------------
// メイン関数
//--------------------------------------------------------------------
int main(void)
{
    int   i;
    char  *c;
    
    //----------------------------------------------------------------
    // ポインタを返す関数へのポインタ。関数の返すポインタは、
    // 5つの要素を持つ2次元配列を指す。ただし、2次元目のアドレスを指すらしい。
    //----------------------------------------------------------------
    char (*(*var)())[5];
    
    var=test1;        // 関数のアドレスを代入

    //----------------------------------------------------------------
    // 関数 test1 を実行し、関数値を c に代入
    // 下の場合、要素数が 0 なので、ahh[0][0]のアドレスになる。
    //----------------------------------------------------------------
    c=(*var)()[0];
    
    for(i=0; i<5 ; i++){
        printf("%p = %c \n", c+i, *(c+i));
    }
    printf("------------------------------------\n");

    //----------------------------------------------------------------
    // 要素数が 1 増えると 5バイト先のアドレスを指す、
    // つまり、ahh[1][0] のアドレスを表すことになる。それを c に代入 
    //----------------------------------------------------------------
    c=(*var)()[1];

    for(i=0; i<5 ; i++){
        printf("%p = %c \n", c+i, *(c+i));
    }
    printf("------------------------------------\n");

    //----------------------------------------------------------------
    // 要素数を 2 にして 10バイト先のアドレスをいじくってみる。、 
    //----------------------------------------------------------------
    c=(*var)()[2];
    
    *((*var)()[2])  ='X';        // アドレスを表すので * を付けて代入 
    *((*var)()[2]+1)='Y';        // アドレスを +1 した所に代入
    *(c+2)='Z';                  // c を使っての代入
    
    for(i=0; i<5 ; i++){
        printf("%p = %c \n", c+i, *(c+i));
    }
    return 0;
}
最後に編集したユーザー どぶろく on 2013年9月14日(土) 17:37 [ 編集 1 回目 ]

どぶろく
記事: 75
登録日時: 12年前

Re: 恐怖のマニュアル

投稿記事 by どぶろく » 11年前

では、いよいよ問題の

char *(*(*var)())[10];

にいきましょう。いやー、本題までが長かったですね。

//------------------------------------------------------------------------
*(*var)()        //「ポインタを返す関数」へのポインタ
(*(*var)())[10]      // 関数が返すポインタは2次元目を指す
*(*(*var)())[10]     // その配列は、ポインタの2次元配列である
char *(*(*var)())[10]   // ポインタである2次元配列は char型を指す
//------------------------------------------------------------------------

CODE:

#include 

typedef char *(*PPHH)[5];  // 「ポインタ配列を指すポインタ」の型指定子

//-------------------------------------------------------------------------
//「ポインタ配列を指すポインタ」を返す関数 
//-------------------------------------------------------------------------
PPHH test1(void)
{
    //------------------------------------
    // ポインタの2次元配列をつくる
    //------------------------------------
    static char *phh[2][5]={
                {{"1ABC"},{"2ABC"},{"3ABC"},{"4ABC"},{"5ABC"}},
                {{"6ABC"},{"7ABC"},{"8ABC"},{"9ABC"},{"0ABC"}}
            };

    return phh;
}
//-------------------------------------------------------------------------
// メイン関数
//-------------------------------------------------------------------------
int main(void)
{

    char  *(*(*var)())[5];    // 例の奴
    char  **c;                // C は ダブルポインタ
    int   i;

    var=test1;        // 関数のアドレスを代入

    //----------------------------------------------------------------------
    // c は「ポインタの2次元配列」が指す「2次元配列の2次元目」を指す 
    //----------------------------------------------------------------------
    c=(*var)()[0];
    for(i=0; i<5 ; i++){
        printf("%p = %c \n", *(c+0)+i, *(*(c+0)+i) );
    }
    printf("------------------------------------\n");

    c=(*var)()[1];
    for(i=0; i<5 ; i++){
        printf("%p = %c \n", *(c+0)+i, *(*(c+0)+i) );
    }
    return 0;
}
● char *(*(*var)())[5]; という宣言後の var の解釈(実行時の解釈)
--------------------------------------------------------------------
var       ポインタ変数  // 関数のアドレスを格納する変数
*var      関数      // varが指す関数の実体
(*var)()    関数を実行   // varが指す関数を実行する
(*var)()[5]   添字式     // 関数を実行し、関数が返したポインタを配列へのポ
                // インタにする(2次元配列の2次元目のポインタに
                // なる)。

ISLe
記事: 2650
登録日時: 14年前

Re: 恐怖のマニュアル

投稿記事 by ISLe » 11年前

typedefで定義した型名を使わないと定義できないものへのポインタ型変数を、typedefで定義した型名を使わないで宣言するというのは現実味がないですよね。

どぶろく
記事: 75
登録日時: 12年前

Re: 恐怖のマニュアル

投稿記事 by どぶろく » 11年前

ISLeさん、コメントありがとうございます。
自分でも何が何だかわからないまま例題のプログラムを作成してます。

char *(*(*var)())[5];

結局、これは何なのかということは私にはわかりませんでした。

どぶろく
記事: 75
登録日時: 12年前

Re: 恐怖のマニュアル

投稿記事 by どぶろく » 11年前

● int (*var(int (*)[3]))[3]; の解釈
----------------------------------------
これは「配列を指すポインタ」を返す関数の使用宣言(プロトタイプ宣言)です。つま
り、varは関数であり、配列のポインタを返します。

(int (*)[3])は、引数です。この引数の部分を取ってみると、

int (*var())[3];

となり、varはポインタを返す関数であり、関数が返したポインタは「3つの要素を持
つ配列」を指す。その配列は int型である。となります。では引数の部分、

var(int (*)[3]);

引数の int (*)[3] は、引数になる変数の型を表します。つまり、これ全体が変数名が
省略された型指定子だけの引数チェック用の宣言です。関数 var はポインタを受け取
り、 そのポインタは「3つの要素を持つ配列」を指す。その配列は、int型である。と
なります。

すなわち、関数 varは「3つの要素を持つint型の配列を指すポインタ」を受け取り、
「3つの要素を持つint型の配列を指すポインタ」を返す関数です。

CODE:

#include 

typedef int  (*PHH)[3];  // 「int型の3つの要素を持つ配列」を指すポインタの型指定子

int main(void)
{
    int  (*var(int (*)[3]))[3];      // 関数 var の使用宣言
    // int (*var(PHH))[3];           // 作成した型指定子を使えばシンプル
    // PHH var(PHH);                 // 本当はこれだけだったりして

    int  a[2][3]={{1,2,3},{4,5,6}};  // これまたしょうもない2次元配列を作る 
    int  i, j;

    for(j=0 ; j<2 ; j++){
       for(i=0 ; i<3 ; i++){
           printf("%d ", var(a)[j][i]);
       }                        // 関数 var(a) は、配列のポインタを返すので
       printf("\n");            // そのまま配列名代わりとして使える
    }                           // つまり、関数実行後は  a[j][i] になる
    return 0;
}
//----------------------------------------------------------------
// 使用宣言されているので、main関数の後に定義できる
//----------------------------------------------------------------
PHH var(int (*ph)[3])
{
    return ph;  // めんどくさいので、受け取った配列のポインタをそのまま返す
}

どぶろく
記事: 75
登録日時: 12年前

Re: 恐怖のマニュアル

投稿記事 by どぶろく » 11年前

● int **var[2][3]; の解釈
---------------------------
これは「2重ポインタの2次元配列」になります。つまり、2重ポインタを格納する2
次元配列 var[][] の宣言です。2重ポインタを格納すれば、var[][]は2重ポインタに
なり、int型を指すポインタへのポインタになります。 まずは基本である「ポインタ配
列」からいきましょう。

int *var[3];

なら、「ポインタの配列」になります。*var より、var[] の方が優先順位が高いので
配列 var[] はポインタである。となります。配列はポインタなので、この配列にはア
ドレスが入ることになります、もちろん int型を指します。

int **var[3];

なら、「2重ポインタの配列」になります。配列に入るのは2重ポインタ(int型ポイ
ンタへのポインタ)ということになります。

int *var[2][3];

なら、「ポインタの2次元配列」になります。2次元配列に入るのはポインタ(int型
へのポインタ)ということになります。

int **var[2][3];

なら、「2重ポインタの2次元配列」になります。2次元配列に入るのは2重ポインタ
(int型ポインタへのポインタ)になります。

CODE:

#include 

int  main(void)
{
    // ここでは2次元配列を作る必要はなかったが、記述量を減らすために使用
    int a[2][3]={{0xA1,0xA2,0xA3},{0xA4,0xA5,0xA6}};
    int b[2][3]={{0xB1,0xB2,0xB3},{0xB4,0xB5,0xB6}};
    int c[2][3]={{0xC1,0xC2,0xC3},{0xC4,0xC5,0xC6}};
    int d[2][3]={{0xD1,0xD2,0xD3},{0xD4,0xD5,0xD6}};

    int *pph1[2];      // ポインタ配列の宣言
    int *pph2[2];
    int *pph3[2];
    int *pph4[2];

    int x, y, i, j;    // もろもろの変数を宣言
    int **var[2][2];   //「2重ポインタの2次元配列」を宣言

    pph1[0]=a[0];      // ポインタ配列に2次元目のポインタを代入
    pph1[1]=a[1];      // 2次元配列を作ってなかったら8個の変数や1次元配列
    pph2[0]=b[0];      // を作って代入しなければならなかった。
    pph2[1]=b[1];
    pph3[0]=c[0];
    pph3[1]=c[1];
	pph4[0]=d[0];
    pph4[1]=d[1];

    var[0][0]=pph1;   //「2重ポインタの2次元配列」に、
    var[0][1]=pph2;   //「ポインタ配列名(ポインタ配列へのポインタ)」を代入 
    var[1][0]=pph3;   //「ポインタ配列名」は、2重ポインタと、
    var[1][1]=pph4;   // まったく同じものなので、そのまま代入できる
    
    for(y=0 ; y<2 ; y++){
       for(x=0 ; x<2 ; x++){
          for(j=0; j<2 ; j++){
             for(i=0 ; i<3 ; i++){
                printf("%2X ", var[y][x][j][i]);
             }              // var[y][x] は pph1~pph4 へのポインタなので
          }                 // [j][i]は、pph1[j][i]~pph4[j][i]になる
          printf("\n");
       }
    }
    return 0;
}

どぶろく
記事: 75
登録日時: 12年前

Re: 恐怖のマニュアル

投稿記事 by どぶろく » 11年前

● int *(*var[2])[3]; の解釈
-------------------------------
これは、「int型を指すポインタの2次元配列」を指す var[]の宣言です。

*var[2]

から解釈して、これは「2つの要素を持ったポインタ配列」です。

(*var[2])[3]

ポインタである var[] は「3つの要素を持つ配列(2次元配列)」を指します。

*(*var[2])[3]

var[] が指す「3つの要素を持った配列(2次元配列)」はポインタです。

int *(*var[2])[3];

var[] が指す「3つの要素を持った配列(2次元配列)」はポインタであり int型を指
します。

よって、これは「 int型を指す3つの要素を1次元目に持つポインタの2次元配列の2
次元目」を指す「2つの要素を持ったポインタ配列 var[]」と、その「var[]へのポイ
ンタ変数 var 」の宣言になります。

CODE:

#include 

int main(void)
{
    int  a=1111, b=2222, c=3333, d=4444, e=5555, f=6666;
    int  *phh1[1][3];     //「ポインタの2次元配列」を作る
    int  *phh2[1][3];     // もう一つ作る
    int  *(*var[2])[3];   // 例の奴
    int  n, x, y;

    phh1[0][0]=&a;   // 「ポインタの2次元配列」にアドレスを入れていく
    phh1[0][1]=&b;
    phh1[0][2]=&c;
    phh2[0][0]=&d;
    phh2[0][1]=&e;
    phh2[0][2]=&f;

    var[0]=phh1;     // 例の配列に「ポインタの2次元配列」への
    var[1]=phh2;     // ポインタ(配列名)を入れていく

    for(n=0 ; n<2 ; n++){
       for(x=0 ; x<1 ; x++){
          for(y=0; y<3 ; y++){
             printf("%d ", *(var[n][x][y]) );
             printf("\n");
           }                // var[n] は、phh1~phh2へのポインタ
       }                    // [x][y] は、phh1[x][y]~phh2[x][y] になる
    }                       // phh1[x][y]~phh2[x][y]は、a~fへのポインタ
    return 0;               // var[n][x][y]全体が a~f へのポインタなので
}                           // ポインタに * を付ければ、a~f の内容を表す