66086

[code]

    a[0] = ABC
    a[1] = DE
    a[2] = FGHI
    
[/code]

のようにコマンドプロンプト上に表示できるようにしたつもりだったのですが、

[code]

    a[0] = ABC
    a[1] = DE
    a[2] = FGHI
    a[3] = 
    a[4] = a
    a[5] = +
    
[/code]

と表示されてしまいました。

なぜ while文は a[2] = FGHI の表示で終わらなかったのか、かといって無限に続くわけでもなく a[5] = +
で止まったのか、分かる方いましたら教えていただきたいです。

[code]

    int i = 0;
    char a[][5] = {"ABC","DE","FGHI"};
    
    while (a[i][0] != 0) {
        printf("a[%d] = %s\n", i, a[i]);
        i++;
    }
    
[/code]


CC BY-SA 4.0
2020-04-28T13:54:24.433
66078
2020-04-28T20:29:53.527
2020-04-28T16:43:19.140
3060
39864
question
c
C言語の while 文が思った通りにループしない


`while`ループの継続条件`a[i][0] != 0` から外れる(=終了条件となる)データが用意されていないからです。

[code]

    char a[][5] = {"ABC","DE","FGHI"};
    
[/code]

これだとこの変数の範囲外までアクセスしていき、いつどのように終了するかは不定です。  
`a[5]=+`で止まったのは、`a[6][0]`が`0`だったからで、それは偶然です。  
無効なアドレスをアクセスしたということで異常終了する場合もあります。

上記`while`ループの条件を尊重するなら、初期化データを以下のようにすれば良いでしょう。

[code]

    char a[][5] = {"ABC","DE","FGHI", ""};
    
[/code]

ただ、あまりこういう方法は使われないと思われます。

競技プログラミングのテクニックとかなのかもしれませんが、例えば人の入力により回数/個数が不定の場合のみに使用して、質問のように予めソースコード上で初期化される数が決まっている場合は、その数も定義なり初期化して、使用した方が良いと思われます。  
不定回数入力でも、入力した回数を記録しておいて、それを使用する方が良さそうですし。


CC BY-SA 4.0
2020-04-28T14:16:18.587
66079
2020-04-28T14:30:49.687
2020-04-28T14:30:49.687
26370
26370
66078
answer


`a`の初期化部分は次のコードとほぼ同じです。

[code]

    char a[3][5] = {
        {'A', 'B', 'C', '\0', '\0'},
        {'D', 'E', '\0', '\0', '\0'},
        {'F', 'G', 'H', 'I', '\0'}
    };
    
[/code]

`a`の5x3の二次元配列です。`sizeof(a)`が15になることからもこれがわかります。5の部分は明示されていますが、3の部分は初期化子によって決定されます。

さて、問題は`while`の条件式である`a[i][0] != 0`で`i`が3以上の時です。`a[3]`からは`a`の配列のサイズを越えているため
**未定義の動作**
になります。つまり、何が起きてもおかしくないということです。`a[5]`で止まったのは偶然であり、`[2]`で止まっても、逆にプログラムが落ちても不思議ではありません。理由云々の前に、Cとしては
**間違ったコード(書いてはいけないコード)** となります。

※
未定義の動作とはCの規格上、どのような動作になるのかを定義していない動作のことです。コンパイル時にエラーになる、何もしないでそのまま素通りする、ゴミデータを出力する、エラーになる、プログラムが落ちる、任意コードを実行可能にする等、何が起きてもおかしくないことを意味します。状況によっては脆弱性の原因となりますので、そのような動作がおきないようにコードを書かなければなりません。

* * *

では、どのように修正すべきかです。`a`という配列には終端を示すものはありませんので、終端判定はできません。ですので、ちゃんと`a`のサイズを計算して、その分だけ`i`を回すようにします。

[code]

    #include <stdio.h>
    int main(void)
    {
        char a[][5] = {"ABC","DE","FGHI"};
    
        for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
            printf("a[%d] = %s\n", i, a[i]);
        }
    }
    
[/code]

または、kunifさんの回答のように終端を表す`""`を追加すると言う方法でもいいでしょう。

* * *

未定義の動作であっても、どういう動作をしているのか知りたい…という人もいるかも知れません。ただ、これらの動作は環境や実行状況、コンパイラ、その他色々な要因によって動作が変わるので、よくある動作の一つとして考えてください。

`a`はスタックに積まれることになります。ほとんどのアーキテクチャではスタックは後ろから前に詰まれます。つまり、(たいていの場合は)`a`はスタックの最前線であり、その後ろにも何らかのデータがあり、最適化などがないとたいていの場合は`i`です。さらにその後も呼び出し元でスタックに積まれたデータが入っています(`main`であっても、その`main`を呼び出す前の前処理の何かが入っています)。もし、初期化していない変数とかアライメントによって使われていない領域があっても何からのデータが入っています。それは0かもしれないですし、前に実行した値かも知れませんし、実行時の状況に変わります。なので、その何かが入っている領域に対して、律儀にアクセスし、たまたま`a[6][0]`に相当するところが0だったので止まったと言うだけです。

だいたいこんな感じです。より細かく知りたいのであれば、逆アセンブルして分析をする必要があるでしょう。


CC BY-SA 4.0
2020-04-28T20:29:53.527
66086
2020-04-28T20:29:53.527
7347
66078
answer