web-dev-qa-db-ja.com

多次元配列が明示的に指定されている場合、char [] [] = {{...}、{...}}を使用できないのはなぜですか?

this の記事を読みました。説明されているルールは理解していますが、定数多次元配列を定義し、特定の型の既知の値で直接初期化するときに、コンパイラが次の構文を受け入れるのを正確にブロックするものは何ですか?

const int multi_arr1[][] = {{1,2,3}, {1,2,3}}; // why not?
const int multi_arr2[][3] = {{1,2,3}, {1,2,3}}; // OK

error: declaration of 'multi_arr1' as multidimensional array must have bounds
       for all dimensions except the first

コンパイラが適切に調べず、「サブ配列」ごとに3つの要素を処理していること、またはプログラマーがパスした場合にのみエラーを返す可能性があることをコンパイラが認識できない理由。 {1,2,3}, {1,2,3,4}

たとえば、1D char配列を扱う場合、コンパイラは=の右側の文字列を見ることができ、これは有効です。

const char str[] = "Str";

コンパイラーが配列の次元を推定し、割り当てのサイズを計算できないように、何が起こっているのかを理解したいと思います。コンパイラーが必要なすべての情報を持っているように思えるからです。ここで何が欠けていますか?

24
esgaldir

初期化子から内部次元を推測するようにコンパイラーに要求するには、コンパイラーが標準が回避する方法で遡及的に動作する必要があります。

標準では、初期化されるオブジェクトがそれ自体を参照することを許可しています。例えば:

_struct foo { struct foo *next; int value; } head = { &head, 0 };
_

これは、最初にそれ自体を指すリンクリストのノードを定義します。 (おそらく、後でより多くのノードが挿入されます。)これは、C 2011 [N1570] 6.2.1 7が識別子headに「宣言子の完了直後に始まるスコープがある」と言うため有効です。 declaratorは、宣言の文法の一部です。これには、宣言の配列、関数、および/またはポインターの部分とともに識別子名が含まれます(たとえば、f(int, float)および_*a[3]_は、float f(int, float)または_int *a[3]_)などの宣言における宣言子です。

6.2.1 7のため、プログラマは次の定義を書くことができます。

_void *p[][1] = { { p[1] }, { p[0] } };
_

初期化子_p[1]_を検討してください。これは配列なので、最初の要素へのポインタ_p[1][0]_に自動的に変換されます。 _p[i]_が1の配列であることを知っているため、コンパイラーはそのアドレスを知っています_void *_(iの任意の値に対して)。コンパイラが_p[i]_の大きさを知らなかった場合、このアドレスを計算できませんでした。したがって、C標準で次のように記述できる場合:

_void *p[][] = { { p[1] }, { p[0] } };
_

次に、コンパイラは_p[1]_を超えてスキャンを継続する必要があるため、2番目の次元(この場合は1つだけ)に指定された初期化子の数をカウントできますが、少なくとも_}_までスキャンして確認する必要がありますそして、それはもっと多くの可能性があります)、戻って_p[1]_の値を計算します。

標準では、コンパイラーにこの種のマルチパス作業を強制することを避けています。コンパイラーに内部の次元を推測するように要求することはこの目標に違反するため、標準はそれを行いません。

(実際、標準では、コンパイラが有限量の先読み、おそらくトークン化中に数文字、文法を解析中に単一のトークンを実行することを要求しないかもしれませんが、私にはわかりません。 void (*p)(void) = &SomeFunction;など、リンク時までわからない値がありますが、それらはリンカーによって入力されます。)

27

初期化子の存在下で多次元配列の最も内側の次元を推定するコンパイラーの実装にはimpossibleはありませんが、CまたはC++標準ではサポートされていない機能であり、明らかにその機能が気になるほど大きな需要はありませんでした。

つまり、あなたが求めているものは標準言語ではサポートされていません。それはcould十分な数の人々がそれを必要とするならサポートされます。彼らはしません。

7
Armen Tsirunyan

コメントを簡単に展開するには:

コンパイラーが標準を "ブロック"しているもの(CまたはC++の場合、それらは異なる標準です。いずれかを選択してください)。

標準がこれを許可することを「妨げる」ものは、​​それを実装するための標準提案を書いて誰もそれを受け入れなかったということです。

だから、あなたが求めているのは、あなたが役立つと思うことをやろうとする動機が誰にもなかった理由だけです。

これをまた実装すること、または一貫したセマンティクスを維持することが実際に困難になる場合があります。それは正確にはあなたが尋ねた質問ではありませんが、少なくとも客観的に答えられるかもしれません。十分なモチベーションがあれば、誰かがこれらの困難を乗り越えることができると思います。おそらく誰もいなかったでしょう。

たとえば、( 参照 )の構文a[]は、本当に不明な境界の配列を意味します。境界は、集約初期化を使用して宣言された場合に特殊なケースで推測できるため、a[auto]のようなものとして扱います。たぶんthatは、歴史的な手荷物を持っていないので、より良い提案でしょう。利点が努力を正当化すると思うなら、自由にそれを書いてください。

4
Useless

ルールは、コンパイラは指定された初期化子リストによって配列の最初の次元のみを決定するということです。 2番目の次元が明示的に指定されることを期待しています。限目。

3
haccks

配列の場合、コンパイラーは、インデックス計算を実行できるように、各要素の大きさを知る必要があります。例えば

int a[3];

整数配列です。コンパイラはintの大きさ(通常は4バイト)を知っているため、a[x]のアドレスを計算できます。ここで、xは0と2の間のインデックスです。

2次元配列は、配列の1次元配列と考えることができます。例えば.

int b[2][3];

intの2次元配列ですが、intの配列の1次元配列でもあります。つまり、b[x]は3つのintsの配列を参照します。

配列の配列を使用する場合でも、コンパイラーが各要素のサイズを認識しなければならないという規則は引き続き適用されます。つまり、配列の配列では、2番目の配列は固定サイズでなければなりません。そうでない場合、インデックス作成時にコンパイラがアドレスを計算できませんでした。つまり、b[x]は計算できません。したがって、あなたの例でmulti_arr2を使用する理由は問題ありませんが、multi_arr1はそうではありません。

コンパイラが正しい方向を見て、各「サブ配列」に対して3つの要素を処理していると主張したり、プログラマが次のような場合にのみエラーを返す可能性があると主張する理由。 {1,2,3}、{1,2,3,4}のような各サブ配列の要素数は異なります

おそらくパーサーの制限です。イニシャライザーに到達するまでに、パーサーはすでに宣言を通過しています。初期のCコンパイラはかなり制限されており、上記の動作は、最新のコンパイラが登場するずっと前に予想どおりに設定されていました。

1
JeremyP