C言語の型宣言の解説

# イントロ

C言語の型宣言ってややこしいですよね。
大体の場合は型なんてシンプルなので大丈夫なんですが、
たまにややこしいやつが出てくると悩まされることがあります。

例えば下記、見慣れてると思います。

~~~c
int main(int argc, char * argv[]);
~~~

第二引数のargvですが、正しく理解していますか？
「charの配列へのポインタ」なのか、
それとも、
「charのポインタへの配列」なのか、
答えられますか？

残念な事に、僕は6年間ぐらいコレが曖昧なまま放置していました。

ですが、わかってみると本当はめちゃめちゃ簡単なんです。
単にC言語の解説の際に、なぜかそれが全然解説されていないだけなんです。(要出典)

なので、解説を書こうと思いました。



# 覚えることは7個だけ

覚えなきゃいけないのは以下の7個の規則です。



## 規則1: 「`*`」は「`pointer to ...`」

「`*`」はポインタを表す記号です。知ってますね。
ここでは、「`pointer to ...`」を覚えてください。



## 規則2: 「`[]`」は「`array of ...`」

「`array of ...`」を覚えてください。
`[]`の中には要素数を書けます。



## 規則3: 「`()`」は「`function returning ...`」

「`function returning ...`」を覚えてください。
`()`の中には関数の引数の型を書けます。



## 規則4: 「*」は左からかかる

`A * B` という形の場合、`*`は右側の`B`を修飾しています。
`*`を型に対する単項演算子と考えると、オペランドを右側に取るということです。

例えば、

~~~c
int * a
~~~

であれば、`*`は`a`に掛かっています。`int`には掛かっていません。

~~~c
int ** b;
~~~

であれば、左の`*`は右の`*`に掛かっています。
右の`*`は、`b`に掛かっています。



## 規則5: 「`[]`」は右からかかる

`A[]`のように、`[]`は右側に書かれ、左側にオペランドを取ります。



## 規則6: 「`()`」も右からかかる

`A()`のように、`()`は右側に書かれ、左側にオペランドを取ります。



## 規則7: 「`*`」の結合より、「`[]`」と「`()`」の結合の方が優先度が強い

~~~
* A []
~~~

のように、ある型の両側に`*`と`[]`がある場合には、
`*`と`A`の結合よりも、`A`と`[]`の結合の方が強いため、
まず`A`と`[]`が結合して「`A[]`」となり、
その後で「`*`」と「`A[]`」が結合します。

これまでの6個の規則は、おそらくすぐに覚えられたのではないかと思います。

重要なのはこの規則7です。
ぶっちゃけ、これさえ暗記してしまえばC言語型宣言はマスターできます。
がんばって覚えましょう。

参考までに、僕は「`*`は意外と遅い」と覚えています。
なんとなく、素早く見えるので。「`*`」。


# 変数宣言の読解

上記7規則を理解したら、後は、 **名前のところから** 読んでいきます。

~~~c
char * argv[]
~~~

を例に取りましょう。

まず、名前のところから読み始めるので、`argv`に注目します。

`argv`の両隣には、`*`と`[]`があります。
`*`は右側にオペランドを取るので、一見これは`argv`に掛かっています。(規則5)
`[]`は左側にオペランドを取るので、一見これも`argv`に掛かっています。(規則6)

ここで規則7を考慮すると、`*`より`[]`が優先するので、
`[]`が`argv`に掛かっているとわかります。

なので、「`*`」は、「`argv`」では無く「`argv[]`」に掛かっているとわかります。

最後に、余った「`char`」の部分を拾っておきます。

よって下記の順番で意味が構成されているとわかります。

「`argv`」→「`[]`」→「`*`」→「`char`」

結合順序がわかったら、次にこれを、規則1,2,3を用いて書き換えていきます。
ついでに、最初の名前の後ろにはisを付けます。
すると、下記のようになります。

~~~
argv is    array of    pointer to    char
~~~

これをそのまま英語で解釈すれば型の読解が完了です！
日本語に訳すと、

~~~
argv は char へのポインタ の配列
~~~

となります。



# 関数宣言の読解

この読解法ですが、変数宣言だけでなく、関数宣言にもそのまま適用できます。

~~~c
int main(int argc, char * argv[])
~~~

上記を読解します。

まず、名前の部分が`main`ですね。
次に、`main`の右に`()`があります。
よって、全体としては下記のとおりです。

~~~
結合: 「main」→「()」→「int」
英語: main is    function returning    int
和訳: main は int を返す関数
~~~

さて、`()`の中身は、規則3より、関数の引数の型です。

第一引数は下記のとおりです。

~~~
結合: 「argc」→「int」
英語: argc is    int
和訳: argc は int
~~~

第二引数は先述したとおりです。

~~~
結合: 「argv」→「[]」→「*」→「char」
英語: argv is    array of    pointer to    char
和訳: argv は char へのポインタ の配列
~~~

さて、初めの全体の解釈に、引数の解釈をくっつけて、下記のようになります。

~~~
main は int を返す ( 第一引数は argc で int, 第二引数が argv で char へのポインタ の配列 ) である関数
~~~

というわけです。



# 関数ポインタの読解

当然関数ポインタも楽勝です。

~~~c
void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *));
~~~

上記を読解します。

全体は下記の通り。

~~~
qsort is    function returning    void
~~~

qsortの第一、第二、第三引数は下記の通り。

~~~
base is    pointer to    void
nel is    size_t
width is    size_t
~~~


第4引数`compar`を丁寧に見ていきます。

`compar`を囲む丸カッコがありますね。
これは式を書く時の丸カッコと同じで、演算子間の結合優先度を制御するためのものです。

なので、`compar`にはまず、`*`が左から掛かっていることがわかります。

「`(*compar)`」に対しては、右側から`()`が掛かっています。

最後に`int`が残ります。

よって、`compar`は下記のとおりです。

~~~
結合: 「compar」→「*」→「()」→「int」
英語: compar is    pointer to    function returning    int
和訳: compar は int を返す関数 へのポインタ
~~~

そして、`()`の中には関数の引数の型があります。

名前が省略されていますが、同じようにやります。

~~~
結合: 「*」→「const void」
英語: pointer to    const void
和訳: const void へのポインタ
~~~


よって以上をまとめて、下記のとおりです。

~~~
qsortはvoidを返す(
  第一引数がbaseでvoidへのポインタ、第二引数がnelでsize_t、第三引数がwidthでsize_t,
  第四引数がcomparでintを返す(
    第一引数がconst voidへのポインタ、第二引数がconst voidへのポインタ
  )である関数へのポインタ
)である関数
~~~



## 関数ポインタのパターン

`argv`の結合と、`compar`の結合を見比べてみてください。
`argv`の場合はまず`[]`が結合し、次に`*`が結合しています。
`compar`では、`*`が結合した後に`()`が結合しています。

規則7の通り、`*`と`()`,`[]`では、後者達の方が結合が強いので、
両側にそのまま書いた`argv`では`[]`が先に結合して、`*`が後に結合しています。

一方、`compar`では、句を括るための`()`を使うことで、
規則7の優先順位を変更し、まず`*`を結合させ、次に`()`を結合させています。

この、「`*`」→「`()`」の順番が、「`pointer to    function`」となり、
関数ポインタの型記述となるのです。

関数ポインタは`pointer to function`ですから、
この「`*`」→「`()`」の結合をさせるために、
名前と`*`を丸括弧で囲むのは基本パターンとなります。
その結果、下記のようなイディオムが導かれるわけです。

~~~
関数の返り値の型 (*関数ポインタ名)(引数)
~~~

これで、関数ポインタ名のところを囲っていた`()`の意味がはっきりしたと思います。



## 関数と関数ポインタ

関数ポインタは`pointer to function`でした。
そこで、この、`pointer to`を取り除いてやれば`function`、
つまり通常の関数宣言になります。

`pointer to`、つまり「`*`」を取り除くと、
当然、「`*`」を名前に結合させるために必要だった丸括弧
も取り除かれるため、下記のようになります。

~~~
関数ポインタのパターン
関数の戻り値の型 (*関数ポインタ名)(引数)

pointer toを取り除いた結果
関数の戻り値の型 関数名(引数)
~~~

すると、必然的に関数宣言の形になっています。

この関係性を理解すると、
関数ポインタの気持ち悪さがだいぶ緩和されます。



# 型宣言を組み立てる

これまでのやり方を、逆向きに使えば、
型宣言を記述することが出来ます。

次の例でやってみます。

~~~
intを返す関数ポインタの要素2つの配列へのポインタを返す関数f
~~~

を宣言することにします。
関数ポインタの引数と返り値は`int`, `f`の引数は`int`とします。

英語で考えると以下のようになります。

~~~
f is function(int) returning pointer to array(len=2) of pointer to function(int) returning int
~~~

これを、左側から順番に変換していきます。

~~~
f is
f
~~~

~~~
function(int) returning
f(int x)
~~~

~~~
pointer to
*f(int x)
補足: ()の方が*よりも結合が強いため、このまま左側に*を書き足すだけです。
~~~

~~~
array(len=2) of
(*f(int x))[2]
補足: []の方が*よりも結合が強いため、これまでの部分を丸括弧で囲っておいてから、右側に[]を書き足します。
~~~

~~~
pointer to
*(*f(int x))[2]
補足: []の方が*よりも結合が強いため、このまま左側に*を書き足すだけです。
~~~

~~~
function returning
(*(*f(int x))[2])(int)
補足: ()の方が*よりも結合が強いため、これまでの部分を丸括弧で囲っておいてから、右側に()を書き足します。
上述した、pointer to arrayの「(*)()」パターンです。
~~~

~~~
int
int (*(*f())[2])(int)
~~~

このように、関数宣言が完成します。

型の意味がわかりやすいように、サンプルコードを示します。

~~~c
#include<stdio.h>

int add1(int x){
	return x+1;
}
int add2(int x){
	return x+2; 
}
int sub1(int x){
	return x-1;
}
int sub2(int x){
	return x-2;
}

int (*adds[2])(int) = { &add1, &add2 };
int (*subs[2])(int) = { &sub1, &sub2 };

int (*(*f(int x))[2])(int){
	if(x==1){
		return & adds;
	}else{
		return & subs;
	}
}

int main(int argc, char * argv[]){
	printf("%d,%d\n",
		((*f(argc))[0])(1),
		((*f(argc))[1])(2));
	return 0;
}
~~~



# おわり