パスワードを忘れた? アカウント作成
13457422 journal
プログラミング

espyの日記: C言語のwhile () の条件式にデクリメントで はまった。 5

日記 by espy

もう何ヶ月、何年ぶりか?の書き込み。

趣味的な 16bitマイコンのプログラムで、こんな感じのコードを書いていた:
(簡略化している)

unsigned int tmout = 50000;
while (tmout--){
    if (I2CTxComplete){
        ret = txlen;
        break;
    }
}
if (tmout == 0){
  ret = E_TMOUT;
  if (UCB0STAT & UCB0BUSY) ret = E_BUSY;
  if (UCB0STAT & UCB0NACK) ret = E_NACK;
}
retrun ret;

これはI2Cの送信の関数を抜けるあたりのコードで、送信完了を待って、普通に完了したら送信したバイト数(txlen)を返し、タイムアウトになったらエラーコードを返す(E_BUSYなどは負の値)という仕掛けにした。50000回もステータスを待って完了(I2CTxComplete)しなければ異状ありとみなして、次の if (tmout ==0) で引っかかると思ったが、実際は引っかからなかった。
ブレークポイントを張って、エラー時の tmoutの値を見ると、0xFFFF…あれれ、なんで?

もしや、と思って、cygwin上でこんなプログラムで実験してみると

    j=10;
    while (j--){
        printf("%d ",j);
    }
    printf(" j=%d\n", j);

    j=10;
    while (--j){
        printf("%d ",j);
    }
    printf(" j=%d\n", j);

すると出力は、

$ ./test-while
9 8 7 6 5 4 3 2 1 0 j=-1
9 8 7 6 5 4 3 2 1 j=0

ループを抜けたら j=-1、えーそうなのかー...

コンパイルで、アセンブラソースを出力させて中味を確認してまでやって、ようやく理解した。

while (j--) { 中味; } だと、jの値を評価し、中味を実行するか判定した後で 変数jの値を減らす。(そして判定が真なら中味を実行する)
while (--j) { 中味; } では、変数jを減らしてから jの値を評価し、中味を実行するか決める。

よくよく考えれば、 if ( ) の条件式で単項演算子を使う時は注意だよ、という、ふつーに教科書に載ってる話と全く同じ話なのに、while (条件式) ではカッコ内を処理してから…と思い込んでしまった。単にそれだけの事。

この議論は、espy (3615)によって「 ログインユーザだけ」として作成されている。 ログインしてから来てね。
  • 範囲が狭いかも (スコア:4, 参考になる)

    by hetareDAIO (17407) on 2017年11月17日 7時39分 (#3313939) 日記

    単項演算子のルールは、前置修飾が「演算後の値で評価」、後置修飾が「演算前の値で評価」です。if()とかwhile()の中という覚え方をしていると、例えば

    代入:b = a++;

    関数の引数:val = func(a++)

    三項演算子:val = ( a++ > 10 ? b++: ++c );

    というケースなどで見落とす可能性があります。また、マクロ内での単項演算子とか複数出てきた場合の評価順序とかややこしい問題もありますので、お仕事では単項演算子を評価式内で使うことはコーディングルールで禁止しているケースもあります。老婆心ながらご注意を。

    --
    ほえほえ
    • by ktmizugaki (46208) on 2017年11月17日 15時07分 (#3314191) 日記

      j-- ⇒ jを1減らした後、減らす前の値を返す
      --j ⇒ jを1減らした後、減らした後の値を返す
      ってことだからね。j--が0になったときは、jは-1だなぁ。

      > while (条件式) ではカッコ内を処理してから…と思い込んでしまった。
      って、条件式は、「j--」であって、「j」ではないのだから、「カッコ内を処理してから」で間違っていない。

      --
      svn-init() {
        svnadmin create .svnrepo
        svn checkout file://$PWD/.svnrepo .
      }
      親コメント
    • by hahahash (41409) on 2017年11月17日 11時17分 (#3314056) 日記

      自分も単項演算子を評価式で書かないようにしてますね。

      で、普段使わないもんだから後評価と先評価がどっちか自信がなくて、
      ますます使わなくなる、という……

      親コメント
  • C++では後置インクリメント/デクリメント演算子をオーバーロードするときに、そう振る舞うよう実装しないとまずいよ、という罠も。

  • by delta-keeper (31927) on 2017年12月03日 1時51分 (#3322431) 日記
    while使った条件式でハマリやすいコードですね。
    演算子の前置か後置で評価される値に違いがありますが、仮に前置きにしたところで1カウント差が出てしまう( カウントが1足りない )のでよろしくないですね。

    特にループ変数を条件判定に使いたい場合はwhileの条件式で変数操作せず、while文の最後で操作するようにすると良いと思います。(動作のイメージもそちらの方が付きやすい)
typodupeerror

日々是ハック也 -- あるハードコアバイナリアン

読み込み中...