N.Y.Cityのまちかど

Why_this_loop_is_unwork?

なぜこのループは動作しない?

めちゃめちゃ単純なC言語のプログラムが動かない。同僚の先生に見せられて、なぜこのプログラムが動かないのか、しばらく考えても答えが出せなかったトラップ問題。

問題編

ソース

#include<stdio.h>
#define ARYSIZE (sizeof(list)/sizeof(list[0]))
int list[]={3,1,4,1,5,9,2};

int main(void){
    int i;

    printf("plot start.\n");
    for(i=-1;i<=ARYSIZE-2;i++){
        printf("%d\n",list[i+1]);
    }

    return 0;

}

for文の中身が、よくあるfor文の使い方と違うので少し気持ち悪いですが、全体の構成としてはよくあるforループを回して配列の内容をリストアップするだけの処理に見えます。

コンパイルログ

エラー・ワーニングなし

実行結果

plot start.

冒頭のメッセージは表示されるが、なぜか配列の中身が一切表示されません。なぜ?

検証(ヒント)編

検証のため、以下のようにソースを書き換えてみます。

#include<stdio.h>
#define ARYSIZE (sizeof(list)/sizeof(list[0]))
int list[]={3,1,4,1,5,9,2};

int main(void){
    int i;

    printf("plot start.\n");
    printf("ARYSIZE=%d\n",ARYSIZE);
    for(i=-1;i<=ARYSIZE-2;i++){
        printf("%d\n",list[i+1]);
    }

    return 0;

}

コンパイルログ

Main.c:8:27: warning: format specifies type 'int' but the argument has type 'unsigned long' [-Wformat]
    printf("ARYSIZE=%d\n",ARYSIZE);
                    ~~    ^~~~~~~
                    %lu
Main.c:3:17: note: expanded from macro 'ARYSIZE'
#define ARYSIZE (sizeof(list)/sizeof(list[0]))
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.

ワーニングが出ました(このワーニングはpaiza.ioでコンパイルしたもの)が、コンパイル自体はできています。

実行結果

ARYSIZE=7
plot start.

追加した部分は正常に動いていて、配列の要素数も予定通り表示されていますが、配列の中身はやはり表示されません。

解答・解説

ワーニングに出ていた「argument has type 'unsigned long'」がヒントでした。

マクロARYSIZEは、sizeofの出力結果を割り算して出しています。sizeofが返す値の方はunsigned longなので、それを割り算した結果もunsigned longになります。

ワーニングが出ていたのはunsigned longの値を(signed) intとして表示しようとしていたからです。結果的に(signed) intでも表現できる範囲の値だったので問題なく画面に表示できていました。

配列の中身が一切表示されないのは、そもそもfor文が働いていない可能性が高い。問題はfor文の初期化文(i=-2)も更新文(i++)も問題はなく、原因は比較文(i<=ARYSIZE-2)にありました。変数iは(signed) int、ARYSIZEはunsigned longで、この比較式は型が異なる(ビット幅も符号も異なる)値同士を比較しています。 「JIS X 3010:2003 (ISO/IEC 9899:1999)」には以下のように定義されていました。(6.3章)(下線は引用者による)

  1. まず,一方のオペランドの対応する実数型が long double ならば,他方のオペランドを,型領域を変えることなく,変換後の型に対応する実数型が long double となるように型変換する。
  2. そうでない場合,一方のオペランドの対応する実数型が double ならば,他方のオペランドを,型領域を変えることなく,変換後の型に対応する実数型が double となるように型変換する。
  3. そうでない場合,一方のオペランドの対応する実数型が float ならば,他方のオペランドを,型領域を変えることなく,変換後の型に対応する実数型が float となるように型変換する。+そうでない場合,整数拡張を両オペランドに対して行い,拡張後のオペランドに次の規則を適用する。
    • 両方のオペランドが同じ型をもつ場合,更なる型変換は行わない。
    • そうでない場合,両方のオペランドが符号付き整数型をもつ,又は両方のオペランドが符号無し整数型をもつならば,整数変換順位の低い方の型を,高い方の型に変換する。
    • そうでない場合,符号無し整数型をもつオペランドが,他方のオペランドの整数変換順位より高い又は等しい順位をもつならば,符号付き整数型をもつオペランドを,符号無し整数型をもつオペランドの型に変換する。
    • そうでない場合,符号付き整数型をもつオペランドの型が,符号無し整数型をもつオペランドの型のすべての値を表現できるならば,符号無し整数型をもつオペランドを,符号付き整数型をもつオペランドの型に変換する。− そうでない場合,両方のオペランドを,符号付き整数型をもつオペランドの型に対応する符号無し整数型に変換する。
  4. 浮動小数点型のオペランドの値及び式の結果の値を,型が要求する精度や範囲を超えて表現してもよい。ただし,それによって型が変わることはない

すなわち今回の場合(signed) intとunsigned longの比較を行った結果、符号なし整数型の方が符号ありの方より優先度が高かったため、(singed) intがunsigned longに直されてしまい、マイナスの値を表現できなくなってしまったのです。実際、以下のプログラムを試してみると

#include<stdio.h>

int main(void){
    int i=-1;
    
    printf("%lu\n",(unsigned long)i);
    printf("%lx\n",(unsigned long)i);

    return 0;

}

こんな結果が返ってきました。

18446744073709551615
ffffffffffffffff

これは ARYSIZE-2 = 7-2 = 5 よりはるかに大きい値なので、forループが動くはずはありません。

なお、sizeofの結果の表現範囲を1桁減らして良ければ、以下のようにすれば正常に動作します。

#include<stdio.h>
#define ARYSIZE (sizeof(list)/sizeof(list[0]))
int list[]={3,1,4,1,5,9,2};

int main(void){
    int i;

    printf("plot start.\n");
    printf("ARYSIZE=%d\n",ARYSIZE);
    for(i=-1;i<=(long)ARYSIZE-2;i++){
        printf("%d\n",list[i+1]);
    }

    return 0;

}

=>
plot start.
ARYSIZE=7
3
1
4
1
5
9
2

現在ご覧のページの最終更新日時は2019/10/17 00:47:23です。

Copyright (C) N.Y.City ALL Rights Reserved.

Email: info[at]nycity.main.jp