1. ホーム
  2. c

[解決済み] C言語における配列の初期化に関する混乱

2022-11-19 19:29:06

質問

C言語で配列を初期化する場合、以下のようになります。

int a[5] = {1,2};

とすると、明示的に初期化されていない配列の要素はすべてゼロで暗黙的に初期化されます。

しかし、このように配列を初期化すると

int a[5]={a[2]=1};

printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);

を出力します。

1 0 1 0 0

よくわからないのですが、なぜ a[0] プリント 1 の代わりに 0 ? それは未定義の動作ですか?

注意してください。 この質問は、インタビューの中で聞かれたものです。

どのように解決するのですか?

TL;DR:の動作がおかしいと思うのですが。 int a[5]={a[2]=1}; の動作は、少なくとも C99 ではよく定義されていないと思います。

面白いのは、あなたが質問している部分だけが意味をなしていることです。 a[0] に設定されている 1 というのは、代入演算子は代入された値を返すからです。不明なのはそれ以外の部分です。

もしこのコードが int a[5] = { [2] = 1 } であったとしたら、すべてが簡単だったでしょう。 a[2]1 に、それ以外のものは 0 . しかし { a[2] = 1 } では、代入式を含む非指定イニシャライザがあり、ウサギの穴に落ちてしまいます。


今まで見つけたのはこんな感じです。

  • a はローカル変数でなければなりません。

    6.7.8 初期化

    1. 静的記憶期間をもつオブジェクトの初期化子におけるすべての式は,定数式又は文字列リテラルでなければならない。

    a[2] = 1 は定数表現ではないので a は自動的に保存されなければなりません。

  • a はそれ自身の初期化でスコープに入っています。

    6.2.1 識別子のスコープ

    1. 構造体タグ、ユニオンタグ、列挙タグは、タグを宣言する型指定子において、タグの出現直後から始まるスコープを持ちます。 タグの出現直後から始まります。各列挙定数は、そのスコープを持ちます。 で定義された列挙子の出現直後から始まります。 その他の 他の識別子のスコープは、その宣言子の完了直後から始まります。

    宣言子は a[5] であるため、変数はそれ自身の初期化においてスコープ内にあります。

  • a はそれ自身の初期化で生きています。

    6.2.4 オブジェクトの保存期間

    1. 識別子がリンクなしで宣言されたオブジェクトで、ストレージクラス 指定子なし static 自動保存期間 .

    2. 可変長配列型を持たないこのようなオブジェクトの場合。 その寿命は そのオブジェクトが関連付けられたブロックに入ることから、そのブロックの実行が終了するまで となります。 となります。(囲まれたブロックに入るか、関数を呼び出すと、現在のブロックの実行は中断されますが、終了しません。 の実行を停止する)。ブロックが再帰的に入力される場合、その都度、新しいインスタンス オブジェクトの新しいインスタンスが毎回作成されます。オブジェクトの初期値は不定です。もし オブジェクトに初期化が指定された場合,ブロックの実行中にその宣言に到達するたびに初期化が実行される。 そうでなければ,宣言に到達するたびに値は不定になる。 そうでなければ,値は,その宣言に到達するたびに不定となる。

  • の後にシーケンスポイントがあります。 a[2]=1 .

    6.8 ステートメントとブロック

    1. A 完全な表現 は,他の式や宣言子の一部でない式である。 以下の各項目は完全な式である。 イニシャライザ 式(expression)の中の式(expression) 文の式; 選択文の制御式 ( if または switch );である。 の制御表現 while または do 文の(オプションの)各表現を使用します。 a for 文の中の(オプションの)式は return ステートメントを使用します。 式の終わりはシーケンスポイントです。 式の終わりはシーケンスポイントです。

    なお、例えば int foo[] = { 1, 2, 3 } では { 1, 2, 3 } の部分は中括弧で囲まれた初期化子のリストで、それぞれの初期化子の後にシーケンスポイントがあります。

  • 初期化は初期化リストの順番で行われます。

    6.7.8 初期化

    1. 中括弧で囲まれた各初期化リストには、関連する カレントオブジェクト . 指定がない場合 指定がない場合、現在のオブジェクトのサブオブジェクトは、現在のオブジェクトの型に従った順序で初期化されます。 配列要素は添え字の増加順に、構造体メンバは宣言順に、ユニオンの最初の名前付きメンバは [...]

    1. 初期化は、初期化リストの順番で行われ、特定のサブオブジェクトのために提供された各初期化機能は、同じサブオブジェクトのために以前にリストされた初期化機能を上書きします。 特定のサブオブジェクトのために提供される各イニシャライザーは、同じサブオブジェクトのために以前にリストされたイニシャライザーをオーバーライドする。 明示的に初期化されないすべてのサブオブジェクトは,静的記憶期間をもつオブジェクトと同じように暗黙的に初期化されなければならない。 明示的に初期化されないすべてのサブオブジェクトは、静的な保存期間を持つオブジェクトと同じように暗黙的に初期化されるものとします。
  • ただし、イニシャライザ式は必ずしも順番に評価されるとは限りません。

    6.7.8 初期化

    1. 初期化リスト式の中で副作用が発生する順序は不定です。 未定義です。

しかし、それではまだいくつかの疑問が残ります。

  • シーケンスポイントは関係あるのでしょうか。基本的なルールは

    6.5 表現

    1. 前のシーケンスポイントと次のシーケンスポイントの間で、オブジェクトは、その格納された値 変更されることはありません。 式の評価によって . さらに,先行する値 は、格納される値を決定するためにのみ読み込まれなければならない。

    a[2] = 1 は式ですが、初期化は式ではありません。

    これはAnnex Jと若干矛盾しています。

    J.2 未定義の動作

    • 2つのシーケンスポイント間で、オブジェクトが2回以上変更された場合、または、変更され され、保存される値を決定する以外に以前の値が読み込まれる (6.5)。

    Annex Jでは、式による修正だけでなく、どんな修正もカウントされるとあります。しかし、附属書が非規範的であることを考えると、おそらくそれを無視することができます。

  • サブオブジェクトの初期化は、初期化子式に関してどのように順序付けられるのでしょうか?すべてのイニシャライザーは最初に(ある順序で)評価され、次にサブオブジェクトはその結果で(イニシャライザーリストの順序で)初期化されるのでしょうか?または、それらはインターリーブされることができますか?


私が思うに int a[5] = { a[2] = 1 } は以下のように実行されます。

  1. のストレージ a のストレージは、その含むブロックが入力されたときに割り当てられる。この時点では内容は不定です。
  2. (唯一の)イニシャライザが実行されます ( a[2] = 1 ) が実行され、その後にシーケンスポイントが続きます。これによって 1a[2] を返し 1 .
  3. その 1 を初期化するために使用されます。 a[0] (を初期化します(最初のイニシャライザーは最初のサブオブジェクトを初期化します)。

しかし、ここで物事が曖昧になるのは、残りの要素( a[1] , a[2] , a[3] , a[4] ) は初期化されることになっています。 0 に初期化されるはずですが、いつ初期化されるかは不明です。の前に起こるのでしょうか? a[2] = 1 が評価される前に起こるのでしょうか?もしそうなら a[2] = 1 は "勝利" となり a[2] しかし、ゼロ初期化と代入式の間にシーケンスポイントがないので、この代入は未定義の動作になるのでしょうか?シーケンスポイントは関係あるのでしょうか(上記参照)?それとも、ゼロ初期化はすべてのイニシャライザが評価された後に行われるのでしょうか?もしそうなら a[2] は結局のところ 0 .

C言語規格ではここでの動作を明確に定義していないため、(中略)動作は未定義であると考えています。