前回、C言語の構文の複雑さについて触れましたが、今回は真正面から 構文規則をもとに
const int * pointer_to_const_int; //①
int * const const_pointer_to_int; //②
の2つの違いについて理解したいと思います。
###注意
引用文はすべて規格「JIS X 3010」から引用しています。本記事の引用文はJIS X 3010の著作権者である(財)日本規格協会から許可を得て引用しております。
#宣言
まず、上記①と②は両方とも宣言です。
宣言の構文規則は以下のようになっております。
構文規則
宣言:
宣言指定子列 初期化宣言子並びopt;
宣言指定子列:
記憶域クラス指定子 宣言指定子列opt
型指定子 宣言指定子列opt
型修飾子 宣言指定子列opt
関数指定子 宣言指定子列opt
初期化宣言子並び:
初期化宣言子
初期化宣言子並び , 初期化宣言子
初期化宣言子:
宣言子
宣言子 = 初期化子
(JIS X 3010 6.7章)
宣言は「宣言指定子列 初期化宣言子並びopt;」で構成されると書いてあります。その下に、各要素が細かく分類されていく形で構文規則が書かれています。
末尾にoptとつくものはoptionで、あってもなくてもいいものを表します。
①のうち、まずintは型指定子です。また、constは型修飾子です。
「const int」で宣言指定子列ですね。
*
が構文規則に出てこないので、②の方の宣言指定子列は「int」のみです。
*
は後述の宣言子で出てきますので、*
以降は初期化宣言子並びに含まれることになります。
const int * pointer_to_const_int; //①
[型修飾子][型指定子][________宣言子_________]
[___宣言指定子列__][______初期化宣言子_______]
int * const const_pointer_to_int; //②
[型指定子] [___________宣言子____________]
[宣言指定子列][_________初期化宣言子__________]
#宣言子
初期化宣言子を構成する主要素である宣言子を見ていきます。
構文規則
宣言子:
ポインタopt 直接宣言子
直接宣言子:
識別子
(宣言子)
直接宣言子 [型修飾子並びopt 代入式opt]
直接宣言子 [static 型修飾子並びopt 代入式]
直接宣言子 [型修飾子並び static 代入式]
直接宣言子 [型修飾子並びopt * ]
直接宣言子 ( 仮引数型並び )
直接宣言子 ( 識別子並びopt )
ポインタ:
* 型修飾子並びopt
* 型修飾子並びopt ポインタ
型修飾子並び:
型修飾子
型修飾子並び 型修飾子
仮引数型並び:
仮引数並び
仮引数並び , ...
仮引数並び:
仮引数宣言
仮引数並び , 仮引数宣言
仮引数宣言:
宣言指定子列 宣言子
宣言指定子列 抽象宣言子opt
識別子並び:
識別子
識別子並び , 識別子
(JIS X 3010 6.7.5章)
ここでポインタ型を示す*
が出てきましたね。
まずは焦らずに直接宣言子から確認しましょう。直接宣言子には識別子があります。①②の右端にある、「pointer_to_const_int」「const_pointer_to_int」は識別子です。
次に宣言子の左側を構成する、「ポインタopt」に注目します。①の場合から見て行きましょう。
前述のとおり、①で宣言子に該当するのは* pointer_to_const_int
の部分です。ここから識別子を除くとポインタにあたる*
のみが残ります。これで最後まで分解出来ましたね。
さらに②を見ると、宣言子に該当するのは* const const_pointer_to_int
です。先ほどと同様、識別子を除くとポインタにあたるのは* const
です。ポインタは型修飾子並びを含むため、constも含まれます。
##宣言子の意味規則
分解できたところで、その構文の意味について見ていきます。まずは、一般的な宣言子の意味規則について。
次の宣言を考える。
T D1
ここで T は,型T(例えば int)を指定する宣言指定子列とする。D1 は宣言子とし,D1 に含まれる識別子をここではidentと呼ぶ。宣言子の各形式では,識別子identに対して指定する型をこの記法を用いて帰納的に説明する。
宣言“T D1”において D1 が形式
識別子
をもつ場合,identの型はTとする。宣言“T D1”において D1 が形式
( D )
をもつ場合,identは宣言“T D”によって指定される型をもつ。すなわち括弧内の宣言子は,括弧なしの宣言子と同一となるが,複雑な宣言子の結合が括弧によって変更されることがある。
(JIS X 3010 6.7.5章)
ここで言っているTは宣言指定子列なので、①ならconst int、②ならintになります。ここでは、「D1が形式 識別子 を持つ場合」とありますが、今回のポインタはこれに該当しませんので次に行きます。
##ポインタ宣言子の意味規則
①②はこれに該当します。見て行きましょう。
6.7.5.1ポインタ宣言子
意味規則
宣言“T D1”において,D1 が形式
* 型修飾子並びopt D
をもつ場合,次のとおりとする。宣言“T D”のidentに対して指定される型が“Tの派生宣言子型並び”であれば,“T D1”のidentに対して指定される型は“Tへの型修飾子並びをもつポインタの派生宣言子型並び”とする。identは,並びの中の各型修飾子によって修飾されたポインタとなる。
二つのポインタ型が適合するためには,いずれも同一の修飾がなされていなければならず,かつ両者が適合する型へのポインタでなければならない。
(JIS X 3010 6.7.5.1章)
「D1が形式 * 型修飾子並びopt D をもつ場合」とあるので、①②が該当しますね。
①のT該当部分はconst int
、D1該当部分は* pointer_to_const_int
、identはpointer_to_const_int
です。さらに、「* 型修飾子並びopt D」の中のDはident同様、pointer_to_const_int
です。これを踏まえて、大事なところをもう一度見ます。
宣言“T D”のidentに対して指定される型が“Tの派生宣言子型並び”であれば,“T D1”のidentに対して指定される型は“Tへの型修飾子並びをもつポインタの派生宣言子型並び”とする。
(JIS X 3010 6.7.5.1章)
T,D,D1,identを先ほど解析したものに置き換えてみましょう。
宣言“const int pointer_to_const_int”のpointer_to_const_intに対して指定される型が“const int”であれば,“const int * pointer_to_const_int”のpointer_to_const_intに対して指定される型は“const intへのポインタ”とする。
注:“派生宣言子型並び”とはポインタ型・配列型・関数型の総称になります。ここでは派生元である通常の型も含まれてもよいと考えて省きました。また、"型修飾子並びを持つ"の部分は、①については宣言子内に型修飾子を持たないため省きました。(わかりやすくするためです。②の場合やダブルポインタの場合などは省けません。)
これで①は見事に解析できました。
②についても同様に見ましょう。
②のT該当部分はint
、D1該当部分は* const const_pointer_to_int
、identはconst_pointer_to_int
です。さらに、「* 型修飾子並びopt D」の中のDはident同様、const_pointer_to_int
です。これで①と同様に置き換えると以下のようになります。
宣言“int const_pointer_to_int”のconst_pointer_to_intに対して指定される型が“int”であれば,“int * const const_pointer_to_int”のconst_pointer_to_intに対して指定される型は“intへのconst修飾子をもつポインタ”とする。
注:今回は"型修飾子並びを持つ"の部分を省略せず、"const修飾子を持つ"に置き換えました。
以上で②も解析できました。
#まとめ
①と②の両方の場合について、構文規則からその意味を理解することが出来ました。
ところで、型修飾子は宣言指定子と宣言子の両方の構文規則に出てきたのにお気づきでしょうか。このどちらに出てくるかで意味が異なることに気づくと、constの振る舞いの複雑さも紐解いて見ることができます。
C言語の構文は読みにくいところもありますが、コンパイラが解析できるのですから厳格な構文規則の上に成り立っていることは確かです。みなさんも「ん?」と思ったら構文規則を見なおしてみてはいかがでしょうか。
この記事の引用元であるC99のJIS規格書はWEBで見ることができます。
ご覧になりたい方は、日本工業標準調査会のJIS検索ページで、X3010で検索してください。