みけCATのにっき(仮)
つれづれなるまゝに、日くらし、PCにむかひて、心に移りゆくよしなし事を、そこはかとなく書きつくれば、あやしうこそものぐるほしけれ。
(本当か!?)
出典

Arduinoのリファレンスに書かれた罠

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Arduinoのリファレンスに書かれた罠

投稿記事 by みけCAT » 9年前

ひと目で尋常でないトラップだと見抜いたよ ※読点が無いのは本編準拠であり、仕様です
[nico]http://www.nicovideo.jp/watch/1397552685[/nico]

というわけで、「Arduino 日本語リファレンス」(WebArchive)にひどい罠を見つけたので、紹介します。
Arduino 日本語リファレンス さんが書きました:min()、max()、abs()の各関数は実装の都合により、カッコ内で関数を使ったり変数を操作することができません。たとえば、min(a++, 100)とすると正しい答が得られません。かわりに、次のようにしてください。
 a++;
 min(a, 100);
aの型が書かれていませんが、int型だとすると最初の実装と次の実装でmin関数に「渡されそうな値」が違うことは、
賢明な皆さんであればすぐにわかるでしょう。
a++は後置インクリメントなので、インクリメントする前の値が返ります。
しかし、aをmin関数に渡す前にインクリメントしてしまうと、当然インクリメントした後の値が渡されてしまいます。

しかし、普通のC言語では渡されそうな値が違いそうですが、もしかしたらArduinoの言語だと違わないのかもしれません。
念のため実際に検証してみましょう。

CODE:

void setup() {
  Serial.begin(9600);
}

int my_min(int a, int b) {
  return a <= b ? a : b;
}

void print_data(int i, int a, int ret) {
  Serial.print("i = ");
  Serial.print(i, DEC);
  Serial.print(", a = ");
  Serial.print(a, DEC);
  Serial.print(", ret = ");
  Serial.print(ret, DEC);
  Serial.println();
}

void wrong_usage() {
  Serial.println("----- wrong usage");
  for (int i = 98; i <= 102; i++) {
    int a = i;
    int ret;
    ret = min(a++, 100);
    print_data(i, a, ret);
  }
}

void reference_usage() {
  Serial.println("----- reference usage");
  for (int i = 98; i <= 102; i++) {
    int a = i;
    int ret;
    a++;
    ret = min(a, 100);
    print_data(i, a, ret);
  }
}

void func_usage() {
  Serial.println("----- func usage");
  for (int i = 98; i <= 102; i++) {
    int a = i;
    int ret;
    ret = my_min(a++, 100);
    print_data(i, a, ret);
  }
}

void loop() {
  switch (Serial.read()) {
    case 'w':
      wrong_usage();
      break;
    case 'r':
      reference_usage();
      break;
    case 'f':
      func_usage();
      break;
  }
}
書き込んだあと、シリアルモニタを開きます。
まずはfを送信して「普通」の関数で実装したmy_minについてmy_min(a++, 100)を実行した結果。

CODE:

----- func usage
i = 98, a = 99, ret = 98
i = 99, a = 100, ret = 99
i = 100, a = 101, ret = 100
i = 101, a = 102, ret = 100
i = 102, a = 103, ret = 100
直感的な動作をしています。

次に、リファレンスに載っている「間違った」使い方を試してみましょう。wを送信します。

CODE:

----- wrong usage
i = 98, a = 100, ret = 99
i = 99, a = 101, ret = 100
i = 100, a = 101, ret = 100
i = 101, a = 102, ret = 100
i = 102, a = 103, ret = 100
retの値が一致しない上、aの値が2増加してしまっている場合があります。「正しい答が得られません」と書いてあるので、仕様ですね。

そして、リファレンスで「推奨された」書き方を試してみましょう。rを送信します。

CODE:

----- reference usage
i = 98, a = 99, ret = 99
i = 99, a = 100, ret = 100
i = 100, a = 101, ret = 100
i = 101, a = 102, ret = 100
i = 102, a = 103, ret = 100
aの値は常に1増加するようになりましたが、retの値が直感に反しています。

さらに、原文にも同様の罠が書かれています。
Arduino - Min さんが書きました:min(a++, 100); // avoid this - yields incorrect results

a++;
min(a, 100); // use this instead - keep other math outside the function
(http://arduino.cc/en/Reference/Min) (WebArchive)

日本語版にはなさそうですが、max関数、abs関数についてもご丁寧に同様の罠が書かれています。
Arduino - Max さんが書きました:max(a--, 0); // avoid this - yields incorrect results

a--; // use this instead -
max(a, 0); // keep other math outside the function
(http://arduino.cc/en/Reference/Max) (WebArchive)
Arduino - Abs さんが書きました:abs(a++); // avoid this - yields incorrect results

a++; // use this instead -
abs(a); // keep other math outside the function
(http://arduino.cc/en/Reference/Abs) (WebArchive)

GitHubにもIssueとして投げておきました。
The warnings in reference of min(), max() and abs() seem wrong · Issue #2602 · arduino/Arduino

アバター
nullptr
記事: 239
登録日時: 12年前

Re: Arduinoのリファレンスに書かれた罠

投稿記事 by nullptr » 9年前

つまり、最初からC++を使えばいいってことですね!