18

以下の配列を初期化すると、 を除いてすべての出力が正常に見えますvalues[3]。何らかの理由でvalues[3]初期化されvalues[0]+values[5]、非常に大きな数値が出力されます。私の推測ではvalues[0]+values[5]、メモリに適切に保存される前に割り当てようとしているのですが、誰かがそれを説明できれば素晴らしいでしょう。

int main (void)
{

    int values[10] = { 
        [0]=197,[2]=-100,[5]=350,
        [3]=values[0] + values[5],
        [9]= values[5]/10
    };

    int index;

    for (index=0; index<10; index++)
        printf("values[%i] = %i\n", index, values[index]);


    return 0;
}

出力は次のとおりです。

values[0] = 197
values[1] = 0
values[2] = -100
values[3] = -1217411959
values[4] = 0
values[5] = 350
values[6] = 0
values[7] = 0
values[8] = 0
values[9] = 35
4

5 に答える 5

16

ドラフト C99 標準セクションから、初期化リスト式の評価の順序が指定されていないため、ここでは指定されていない動作の対象になっているようです6.7.8

初期化リストの式の中で副作用が発生する順序は規定されていません。133)

ノート 133 は次のように述べています。

特に、評価順序は、サブオブジェクトの初期化の順序と同じである必要はありません。

私が知る限り、メモをバックアップする規範的なテキスト133はセクションからのもの6.5です:

後で指定する場合を除き [...] 部分式の評価の順序と副作用が発生する順序はどちらも指定されていません。

そして、初期化子が完全な式であることがわかります6.8(強調鉱山):

完全な式は、別の式または宣言子の一部ではない式です。 次のそれぞれは完全な式です。[...]

初期化子内のシーケンスポイントをカバーし、完全な式を別の場所に配置する私の古いC++の回答の1つを振り返った後、最初に結論付けた後、含まれている初期化子の文法に2回気づきました。6.7.8

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designationopt initializer
    initializer-list , designationopt initializer

私はもともとこれに気づいておらず、上記の文法のトップ要素に適用される完全表現に関するステートメントを考えていました。

C++ のように、完全な式が初期化子リスト内の各初期化子に適用されるため、以前の分析が正しくないと考えています。

欠陥レポート 439は、これが事実であるという私の疑いを裏付けるもので、次の例が含まれています。

#include <stdio.h>

#define ONE_INIT      '0' + i++ % 3
#define INITIALIZERS      [2] = ONE_INIT, [1] = ONE_INIT, [0] = ONE_INIT

int main()
{
    int i = 0;
    char x[4] = { INITIALIZERS }; // case 1
    puts(x);
    puts((char [4]){ INITIALIZERS }); // case 2
    puts((char [4]){ INITIALIZERS } + i % 2); // case 3
}

そしてそれは言います:

INITIALIZERS マクロを使用するたびに、変数 i が 3 回インクリメントされます。ケース 1 と 2 の場合、未定義の動作はありません。これは、インクリメントが、順序付けされていないのではなく、互いに不定に順序付けられた式にあるためです。

したがって、内部の各初期化子INITIALIZERS完全な式です。

この欠陥レポートは C11 に対するものであるため、この問題に関する規範的なテキストでは、C11 は C99 よりも詳細であり、次のように記載されていることに注意してください。

初期化リストの式の評価は、相互に不定な順序で行われるため、副作用が発生する順序は指定されていません。152)

valuesの各要素が割り当てられる前に次の式が評価される場合、未定義の動作があります。

 values[0] + values[5]

また:

 values[5]/10

不確定な値を使用すると未定義の動作が呼び出されるため、これは未定義の動作です。

この特定のケースでは、最も簡単な回避策は、手動で計算を実行することです。

int values[10] = { 
    [0]=197,[2]=-100,[5]=350,
    [3]= 197 + 350,
    [9]= 350/10
};

3要素への割り当てや初期化の後に行うなど、他の選択肢があり9ます。

于 2015-03-02T15:56:11.607 に答える
7

これは、指定されたイニシャライザ自体とは関係ありません。これは、次のようなことを試みたときに発生するのと同じバグです。

int array[10] = {5, array[0]};

初期化リストの式が実行される順序は、指定されていない動作です。つまり、コンパイラ固有であり、文書化されておらず、決して依存すべきではありません。

C11 6.7.9/23

初期化リストの式の評価は、相互に不定な順序で行われるため、副作用が発生する順序は指定されていません。

配列項目を使用して他の配列メンバーを初期化しているため、コードを初期化ではなくランタイム割り当てに変更する必要があります。

  int values[10];

  values[2] = -100;
  values[5] = 350;
  values[3] = values[0] + values[5];
  ...

副作用として、プログラムがはるかに読みやすくなります。

于 2015-03-02T16:01:35.510 に答える
5

そのように初期化されたものを見たのはこれが初めてですが、あなたが見ている動作は、まだ初期化されていない配列の一部にアクセスすることに関係していると考えました. そこで、32 ビットの Ubuntu 12.04 システムで GCC 4.6.3 を使用してビルドしました。私の環境では、あなたとは異なる結果が得られました。

gcc file.c -o file

./file
values[0] = 197
values[1] = 0
values[2] = -100
values[3] = 197
values[4] = 0
values[5] = 350
values[6] = 0
values[7] = 0
values[8] = 0
values[9] = 35


objdump -d file > file.asm

cat file.asm     (relevant portion posted below)

080483e4 <main>:
 80483e4:   55                      push   %ebp
 80483e5:   89 e5                   mov    %esp,%ebp
 80483e7:   57                      push   %edi
 80483e8:   53                      push   %ebx
 80483e9:   83 e4 f0                and    $0xfffffff0,%esp
 80483ec:   83 ec 40                sub    $0x40,%esp
 80483ef:   8d 5c 24 14             lea    0x14(%esp),%ebx
 80483f3:   b8 00 00 00 00          mov    $0x0,%eax
 80483f8:   ba 0a 00 00 00          mov    $0xa,%edx
 80483fd:   89 df                   mov    %ebx,%edi
 80483ff:   89 d1                   mov    %edx,%ecx
 8048401:   f3 ab                   rep stos %eax,%es:(%edi)   <=====
 8048403:   c7 44 24 14 c5 00 00    movl   $0xc5,0x14(%esp)
 804840a:   00 
 804840b:   c7 44 24 1c 9c ff ff    movl   $0xffffff9c,0x1c(%esp)
 8048412:   ff
 8048413:   8b 54 24 14             mov    0x14(%esp),%edx
 8048417:   8b 44 24 28             mov    0x28(%esp),%eax
 804841b:   01 d0                   add    %edx,%eax
 804841d:   89 44 24 20             mov    %eax,0x20(%esp)
 8048421:   c7 44 24 28 5e 01 00    movl   $0x15e,0x28(%esp)
 8048428:   00 
 8048429:   8b 4c 24 28             mov    0x28(%esp),%ecx
 804842d:   ba 67 66 66 66          mov    $0x66666667,%edx
 8048432:   89 c8                   mov    %ecx,%eax
 8048434:   f7 ea                   imul   %edx
 8048436:   c1 fa 02                sar    $0x2,%edx
 8048439:   89 c8                   mov    %ecx,%eax
 804843b:   c1 f8 1f                sar    $0x1f,%eax

上記の出力で、あなたが生成したものと私の生成したものとの違いを示していると思われる重要な行を特定しました(<======でマークされています)。特定の配列要素が指定した値で初期化される前に、配列の内容をゼロにしています。この後、配列要素の特定の初期化が行われます。

上記の動作を考えると、配列の特定の要素を初期化する前に配列の内容をゼロにしなかったという仮説を立てるのは不合理ではないと思います。行動の違いはなぜですか?推測することしかできません。しかし、私の最初の推測では、2 つの異なるコンパイラ バージョンを使用しているということです。

お役に立てれば。

于 2015-03-02T16:03:23.050 に答える
4
int values[10] = { 
    [0]=197,[2]=-100,[5]=350,
    [3]=values[0] + values[5],
    [9]= values[5]/10
};

編集:

ISO C99 標準のセクション 6.7.8 (初期化)では、次のように指定されています。

初期化はイニシャライザ リストの順序で行われ、特定のサブオブジェクトに提供される各イニシャライザは、同じサブオブジェクトの以前にリストされたイニシャライザをオーバーライドします。

しかし、Shafikが指摘したように、評価順序は初期化順序と一致する必要はありません。

つまりvalues[0] + values[5]、以下からガベージ値を読み取る可能性があります。

  • values[0]
  • values[5]これはあなたの場合に起こることです
  • 両方
  • そのなかで何も
于 2015-03-02T15:53:59.790 に答える
1

このコードを試してください:

int values[10];
values[0]=197;
values[2]=-100;
values[5]=350;
values[3]=values[0]+values[5];
values[9]=values[5]/10;

そして、行ったように配列を印刷します。

于 2015-03-02T16:13:21.880 に答える