配列の規格に沿った読み方について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
sys
記事: 27
登録日時: 14年前

配列の規格に沿った読み方について

#1

投稿記事 by sys » 14年前

長文で失礼します。2次元配列の規格に沿った読み方についての質問です。(環境 Xcode 3.2.5 64bit)
char two_array[2][3] = { "ab", "cd" }; のとき
私が理解している範囲で two_array[0][0] を読むと、
E1[E2]が (*((E1)+(E2)))と等価であると定義する。
[C99 6.5.2.1 から一部引用]
から
two_array[0][0] → (*((two_array)+(0)))[0] → (*((*((two_array)+(0)))+(0)))
となり、簡素化すると
(*(*(two_array)))
と読む。

(*(*(two_array))) を演算子の優先順位に従い
two_array → *two_arrayへのポインタ
* → *two_arrayへのポインタを間接参照 = **two_arryaへのポインタ
* → **two_arrayへのポインタを間接参照 = 'a'
と読んでいました。

しかし規格には、
左辺値が3つの例外(sizeof演算子のオペランド、単項&演算子のオペランド、文字列配列を初期化するのに使われる文字列リテラル)を除く [〜型の配列] をもつ式は、[〜型へのポインタ] の式に型変換する。
それは配列オブジェクトの先頭の要素を指し、左辺値ではない。
[C99 6.3.2.1 から一部引用]
と定義してあり、
two_array → *two_arrayへのポインタ
は、間違いであり
two_array → 配列オブジェクトの先頭の要素へのポインタ
となる。

そうすると、 char array[5] = "abcd"; で1次元配列の array[0] なら
array[0] → (*(array)) を演算子の優先順位に従い
array → 配列オブジェクトの先頭の要素へのポインタ
* → 配列オブジェクトの先頭の要素へのポインタを間接参照 = 先頭の要素( = 'a')
と規格通りに読むことが出来るのですが、2次元配列の two_array[0][0] を[C99 6.3.2.1]の通りに読むと、
two_array[0][0] → (*(*(two_array))) を演算子の優先順位に従い
two_array → 配列オブジェクトの先頭の要素へのポインタ
* → 配列オブジェクトの先頭の要素へのポインタを間接参照 = 先頭要素( = 'a')
* → 先頭要素( = 'a')を間接参照??(先頭要素( = 'a')の型はポインタじゃないので[C99 6.5.3.2 アドレス及び間接演算子]の定義により間接参照できない。)
と2個目の間接参照を無視した形になってしまうと思います。

two_array が指しているメモリを調べて(%p ← two_array)、先頭要素(two_array[0][0] = (*(*(two_array))) )を指しているのは確認しました。
ただ (*(*(two_array))) を読むときに、
two_array → 配列オブジェクトの先頭の要素へのポインタ
と読んでしまうと、 ** の2重間接参照の説明がつきません。

なぜ [ two_array → *two_array へのポインタ ] と読んだかというと、ポインタ演算の定義の、
ポインタ型の式[P]が配列オブジェクトの第i要素を指すなら、[(P)+N]は、「Pのアドレス+n×オブジェクトの大きさ」のアドレスを指す。
[C99 6.5.6 並びに 新ANSI C言語辞典 p284 加算演算子 から一部引用]
から、ポインタに1加算すると、そのポインタが指す型のサイズ増加する。(この解釈が違う??)
two_array+1 は、アドレスが3増加 (3増加する型はchar[3])
型がchar[3]のポインタは *two_array である。(typeid(*two_array).name() → A3_c)
つまり、ポインタ(two_array)が指すのは *two_array であるから 3増加する。
と考えて読みました。
two_array が配列の先頭要素(two_array[0][0] = (*(*(two_array))) )を指しているポインタだとすると、
(*(*(two_array))) の型はcharなので (typeid(**two_array).name() → c) 1増加になるはずです。
以上のポインタ演算の定義から考えると、two_array は、 *two_array を指していると読むことができてしまいます。

例えば [C99 6.3.2.1] に定義されている他に、
配列名が、配列の先頭要素へのポインタを指すのはある限定的な場合に限る
のような定義が、どこかに定義してあるなら全てが納得出来ますが、見つけることが出来ませんでした。

厳密な読み方がわからなくても *two_array、**two_array、が式の中でどうゆう結果になるのかはわかりますが、その課程を規格に沿った形で読む方法が知りたいです。
ご教示お願い致します。

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: 配列の規格に沿った読み方について

#2

投稿記事 by YuO » 14年前

sys さんが書きました:そうすると、 char array[5] = "abcd"; で1次元配列の array[0] なら
array[0] → (*(array)) を演算子の優先順位に従い
array → 配列オブジェクトの先頭の要素へのポインタ
* → 配列オブジェクトの先頭の要素へのポインタを間接参照 = 先頭の要素( = 'a')
と規格通りに読むことが出来るのですが、2次元配列の two_array[0][0] を[C99 6.3.2.1]の通りに読むと、
two_array[0][0] → (*(*(two_array))) を演算子の優先順位に従い
two_array → 配列オブジェクトの先頭の要素へのポインタ
* → 配列オブジェクトの先頭の要素へのポインタを間接参照 = 先頭要素( = 'a')
* → 先頭要素( = 'a')を間接参照??(先頭要素( = 'a')の型はポインタじゃないので[C99 6.5.3.2 アドレス及び間接演算子]の定義により間接参照できない。)
と2個目の間接参照を無視した形になってしまうと思います。
なりません。

まず,two_array自体について。
sys さんが書きました:

コード:

char two_array[2][3] = { "ab", "cd" };
なので,two_arrayの型は"array(2 elements) of array(3 elements) of char"です。

次に,配列自体がオブジェクトで,配列自体が配列の要素になります。
ISO/IEC 9899:9999 6.2.5 Typesの第20段落の列挙の後に,
These methods of constructing derived types can be applied recursively.
とあるため,type derivationは再帰的に適用できます。そして,列挙中に
An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes called “array of T”.
とありますので,
  • "char"をarray type derivationすると"array of char" (要素型はchar)
  • "array of char"をarray type derivationすると"array of array of char" (要素型はarray of char)
となります。

さて,6.3.2.1 Lvalues, arrays, and function designatorsの第3段落の文は次のようになっています。
an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue.
two_arrayにおいて,typeに相当するのは"array of char"です。
6.2.5の記述により,element typeはtypeそのもの,ということになります。
つまり,"the initial element of the array object"とはchar [3]型を持つオブジェクトで,初期化時に"ab"が指定されている要素になります。
定義がループすることを承知で書くなら,"the initial element of the array object"はtwo_array[0]です。
なので,6.3.2.1の第3段落のconversionが発生後のtwo_arrayは,やはり定義がループすることを承知で書くと,&(two_array[0])になります。

ここまでが前置きです。


さて,本題のtwo_array[0][0]ですが,
*(*(two_array + 0) + 0)
とみなして,
  • two_array
    array of array of char型のオブジェクト → two_arrayの先頭要素 (array of char型) へのポインタ
    →6.3.2.1の適用
  • two_array + 0
    two_arrayの先頭要素 (array of char型) へのポインタ
    →+0なので表記上変化はない
  • *(two_array + 0)
    two_arrayの先頭要素 (array of char型) → two_arrayの先頭要素 (array of char型) の先頭要素 (char型) へのポインタ
    →単項*演算子を適用したので,「~へのポインタ」が消えた/6.3.2.1の適用
  • *(two_array + 0) + 0
    two_arrayの先頭要素 (array of char型) の先頭要素 (char型) へのポインタ
    →+0なので表記上変化はない
  • *(*(two_array + 0) + 0)
    two_arrayの先頭要素 (array of char型) の先頭要素 (char型)
    →単項*演算子を適用したので,「~へのポインタ」が消えた
となります。

sys
記事: 27
登録日時: 14年前

Re: 配列の規格に沿った読み方について

#3

投稿記事 by sys » 14年前

YuOさん、ご回答ありがとうございます。

[一度17時35分に投稿したのですが、間違えが多かったので削除し訂正しました。]

C99 6.2.5 をふまえると、
two_array は、最初のポインタは*two_array を指しているけど、配列型派生で再帰的に構築された配列型のポインタをたどって、最終的に先頭要素(**two_array)を指しているポインタ(アドレス)を返す。
ということなのでしょうか??

two_array[0][0] をもう一度考えてみました。
  • two_array[0][0] は (*(*(two_array))) と等価。
    1) two_array はchar[2][3]型 ( char('a')へのポインタ(**two_array)へのポインタ(*two_array) )
    次に、「 間接参照演算子 [C99 6.5.3.2] オペランドが [〜型へのポインタ] をもつ場合、その結果は [〜型] 」の定義から
    2) * → char('a')へのポインタ(**two_array)「へのポインタ(*two_array) 」
    「」の部分が消え、char('a')へのポインタ(**two_array) になる。
    次も再び間接参照演算子が適用され
    3) * → char('a')「へのポインタ(**two_array)」
    「」の部分が消え、char('a') となる。
    [結果] **two_array は char('a')
はどうでしょうか?上記の読み方だと、YuOさんの
YuO さんが書きました: *(*(two_array + 0) + 0)
とみなして,
two_array
array of array of char型のオブジェクト → two_arrayの先頭要素 (array of char型) へのポインタ
→6.3.2.1の適用
two_array + 0
two_arrayの先頭要素 (array of char型) へのポインタ
→+0なので表記上変化はない
*(two_array + 0)
two_arrayの先頭要素 (array of char型) → two_arrayの先頭要素 (array of char型) の先頭要素 (char型) へのポインタ
→単項*演算子を適用したので,「~へのポインタ」が消えた/6.3.2.1の適用
*(two_array + 0) + 0
two_arrayの先頭要素 (array of char型) の先頭要素 (char型) へのポインタ
→+0なので表記上変化はない
*(*(two_array + 0) + 0)
two_arrayの先頭要素 (array of char型) の先頭要素 (char型)
→単項*演算子を適用したので,「~へのポインタ」が消えた
とは意味は違ってしまいますか?
正直考えすぎて混乱してきました。

コード:

char *pt;
char **ptpt;
char c = 'a';
pt = &c;
ptpt = &pt;
例えばなのですが、上記コードの **ptpt をたどるとき
  • ptpt は、char**型 で char('a')へのポインタ(pt)へのポインタ
    1) ptptの要素 → ptへのポインタ
    2) * → へのポインタを消し pt となる。
    pt は、char*型 で char('a')へのポインタ
    3) * → へのポインタを消し char('a')となる。
    [結果] **ptpt は char('a') を指す。
と読んでいました。
ポインタ型と配列型での「ポインタの考え方と間接参照の読み方」はぜんぜん違く、
  • charの配列の配列
    1) 「charの配列」の配列
     ↓「」を配列型派生(array type derivation)する↓
    2) 要素型がchar型の配列型 (array of char(要素型はchar) の配列
     ↓配列型派生(array type derivation)する↓
    3) 要素型がchar型の配列型の配列型 (array of array of char(要素型はarray of char)) 
を考えて読むしか無いということでしょうか?

ただポインタ型も派生型(derived type)とのことなので
そもそものポインタと間接参照への考え方が根本的に間違っている気もしてきました、、、。

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: 配列の規格に沿った読み方について

#4

投稿記事 by YuO » 14年前

sys さんが書きました:C99 6.2.5 をふまえると、
two_array は、最初のポインタは*two_array を指しているけど、配列型派生で再帰的に構築された配列型のポインタをたどって、最終的に先頭要素(**two_array)を指しているポインタ(アドレス)を返す。
ということなのでしょうか??
違います。勝手に6.3.2.1を部分に適用してはいけません。
  • two_array
    array of array of char
  • (array to pointer conversion)
    pointer to array of char
としかなりません。
配列もれっきとしたオブジェクトであることが抜けていませんか。
配列は配列であって,ポインタとして扱われるのは特例だと考えてみてはどうでしょうか。

sys
記事: 27
登録日時: 14年前

Re: 配列の規格に沿った読み方について

#5

投稿記事 by sys » 14年前

なるほど、やっとわかりました。

最初に配列への2重間接参照で勘違いしていたのは、C99 6.2.5 第20段落 の
These methods of constructing derived types can be applied recursively.
を知らなかったという点でした。
ポインタ型も派生型ということで、今まで理解していたポインタの挙動が一層納得出来ました。
YuOさんありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る