型「文字列リテラルを指すポインタ」の変数を、型T(*)[]
の値で初期化しようとしたところ、コンパイラに「適合しない型」と怒られたが、どの部分において適合しないのか?という話です。
/* char型配列へのポインタ型(constなし・あり) */
char (*message1)[13] = &"Hello World!"; /* OK */
const char (*message2)[13] = &"Hello World!"; /* warning: initialization from incompatible pointer type */
/* char型へのポインタ型(constなし・あり) */
char *p1 = "Hello World!"; /* OK */
const char *p2 = "Hello World!"; /* OK */
上のコードを書いてコンパイルしたところ、二番目の宣言で警告(initialization from incompatible pointer type
)が出ました。文字列リテラルを指すポインタだから、何も考えずに被参照型にconst
をつけたのですが・・・。
const char (*message2)[13] = &"Hello World!";
$ gcc -o sample -std=c90 -pedantic -Wall sample.c
sample.c: In function ‘main’:
sample.c:6:31: warning: initialization from incompatible pointer type
const char (*message2)[13] = &"Hello World!";
$
#環境
$ gcc --version
gcc (Debian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$
$ uname -a
Linux debian 3.16.0-4-686-pae #1 SMP Debian 3.16.7-ckt11-1+deb8u6 (2015-11-09) i686 GNU/Linux
$
$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.2 (jessie)
Release: 8.2
Codename: jessie
#単純代入演算子のオペランドの型制約
手始めに、単純代入演算子=
がとるオペランドの型制約について規格を確認してみます。
(以降の引用文は、すべて「プログラム言語C JISX3010-1993 (ISO/IEC 9899:1990)」からの引用です。)
6.3.16.1 単純代入
〔...〕
・両オペランドが適合する型の修飾版又は非修飾版へのポインタであり, かつ左オペランドで指される型が右オペランドで指される型の修飾子をすべてもつ。
左オペランドのポインタが指す型の修飾子は、右オペランドのポインタが指す型の修飾子をすべて持っているはず・・・ですが、そもそも両オペランドが「適合する型」ではないのかもしれない。
そこで、両オペランドの型を分解・比較してみます。
- 左オペランドの型の詳細
項目 | 情報 |
---|---|
式 | const char (*message2)[13] |
型分類 | ポインタ型 |
上の被参照型 |
const char[13] (要素数 13 のconst char 型配列型) |
上の要素型 | const char |
- 右オペランドの型の詳細
項目 | 情報 |
---|---|
式 | &"Hello World!" |
型分類 | ポインタ型 |
上の被参照型 |
char[13] (要素数 13 のchar 型配列型) |
上の要素型 | char |
両者とも「ポインタ型」であり、型分類は一致しています。
では、ポインタ型が適合する条件は何か?
#ポインタ型の適合条件
6.5.4.1 ポインタ宣言子
〔...〕
二つのポインタ型が適合するためには, どちらも同一の修飾がなされていなければならず, かつ両者が適合する型へのポインタでなければならない。
両オペランドとも、ポインタ型自身は非修飾版なので、規定の前半部分には問題ありません。後半部分、つまり両オペランドのポインタ型が指す型は適合する型なのか?というのがポイントになります。
問題コードの場合、両オペランドのポインタ型が指すのは配列型なので、配列型が適合する条件について見てみます。
#配列型の適合条件
6.5.4.2 配列宣言子
〔...〕
二つの配列型が適合するためには, 両者が適合する要素型をもたなければならず, かつどちらも配列の大きさを指定する定数式をもつ場合、それらの値は同じでなければならない。
問題コードでは、左オペランドの(ポインタ型が指す配列型の)要素型がconst char
なのに対して、右オペランドのそれはchar
です。つまり「要素型の型修飾子の有無」が影響していそうです。
そこで、型修飾子の適合条件について見てみます。
#要素型の適合条件・修飾型の適合条件
6.5.3 型修飾子
〔...〕
配列型の指定が型修飾子を含む場合, 要素の型を修飾するのであり, その配列型を修飾するのではない。
〔...〕
二つの修飾型が適合するためには, 両方の型が適合する型の同一の修飾版でなければならない。
つまり、const char[]
は「const char
型の配列」という意味であって「char
型配列のconst
修飾版」という意味でないということ。ここがポイントですね。
ですので、以下のようにtypedef
で回避することもできません。
/*
代入演算子の左オペランド
理想「要素型charの配列型(const修飾版)へのポインタのつもり」
現実「要素型const charの配列型(非修飾版)へのポインタ」
*/
typedef char Array[13];
const Array *message2 = &"Hello World!";
/*
== コンパイル結果 ==
warning: initialization from incompatible pointer type
const Array *message2 = &"Hello World!";
*/
#文字列リテラルの型
ところで、そもそも文字列リテラルの型はconst char []
じゃないの?と思われた方もいるかもしれませんが、C90ではn
バイトの単純文字列リテラルの型はchar [n+1]
です。根拠はこちら(太字引用者)。
6.1.4 文字列リテラル
〔...〕
翻訳フェーズ7において, 文字列リテラル又はその並びから得られる多バイト文字の並びに値0のバイト又はコードを付加する(24)。そして多バイト文字の並びは静的記憶域期間を持ち, かつ文字の並びを含むのにちょうど十分な長さの配列を初期化するのに用いる。単純文字列リテラルに対して, 配列の要素は型char
をもち, 多バイト文字の並びの個々のバイトで初期化する。ワイド文字列リテラルに対して, 配列の要素は型wchar_t
をもち, 多バイト文字の並びに対応するワイド文字の並びで初期化する。
#まとめ
単純代入演算子=
のポインタオペランドに関する「左オペランドで指される型が右オペランドで指される型の修飾子をすべてもつ」という型制約は、両オペランドのポインタが指す型が適合することを前提として、その指される型の修飾子に関する話でした。
問題のコード例は、ポインタが指す型が適合しないため、そもそも前提(「両オペランドが適合する型の修飾版または非修飾版へのポインタ」)にひっかかってしまっていたのですね。
結論として、冒頭のコード例で警告が出たのは「両オペランドのポインタ型が指す配列型の要素型が適合しない型であるため、要素型が適合しない⇒配列型も適合しない⇒ポインタ型も適合しないこととなる。したがって、両オペランドの型も適合しない。」ことが原因と言えます。
#参考文献
- 「プログラム言語C JISX3010-1993 (ISO/IEC 9899:1990)」