72

C言語での不特定の動作の例は、関数への引数の評価の順序です。それは左から右または右から左かもしれません、あなたは知らないだけです。foo(c++, c)これは、評価方法や評価方法に影響しますfoo(++c, c)

知らないプログラマーを驚かせる可能性のある他の不特定の動作はありますか?

4

11 に答える 11

85

言語弁護士の質問。うーん。

私の個人的なトップ3:

  1. 厳密なエイリアシングルールに違反している

  2. 厳密なエイリアシングルールに違反している

  3. 厳密なエイリアシングルールに違反している

    :-)

編集これは2回間違っている小さな例です:

(32ビットintとリトルエンディアンを想定)

float funky_float_abs (float a)
{
  unsigned int temp = *(unsigned int *)&a;
  temp &= 0x7fffffff;
  return *(float *)&temp;
}

そのコードは、floatの表現で符号ビットを直接ビットをいじることによって、floatの絶対値を取得しようとします。

ただし、ある型から別の型にキャストしてオブジェクトへのポインタを作成した結果は有効ではありません。C。コンパイラは、異なる型へのポインタが同じメモリチャンクを指していないと想定する場合があります。これは、void*とchar*を除くすべての種類のポインターに当てはまります(符号は関係ありません)。

上記の場合、私はそれを2回行います。1回はfloataのintエイリアスを取得し、もう1回は値をfloatに変換し直します。

同じことを行うための3つの有効な方法があります。

キャスト中にcharまたはvoidポインタを使用します。これらは常に何かにエイリアスするので、安全です。

float funky_float_abs (float a)
{
  float temp_float = a;
  // valid, because it's a char pointer. These are special.
  unsigned char * temp = (unsigned char *)&temp_float;
  temp[3] &= 0x7f;
  return temp_float;
}

memcopyを使用します。Memcpyはvoidポインターを受け取るため、エイリアシングも強制されます。

float funky_float_abs (float a)
{
  int i;
  float result;
  memcpy (&i, &a, sizeof (int));
  i &= 0x7fffffff;
  memcpy (&result, &i, sizeof (int));
  return result;
}

3番目の有効な方法:ユニオンを使用します。これは、C99以降、明示的に未定義ではありません。

float funky_float_abs (float a)
{
  union 
  {
     unsigned int i;
     float f;
  } cast_helper;

  cast_helper.f = a;
  cast_helper.i &= 0x7fffffff;
  return cast_helper.f;
}
于 2008-09-19T00:33:12.910 に答える
32

私の個人的なお気に入りの未定義の動作は、空でないソース ファイルが改行で終わらない場合、動作が未定義になるというものです。

警告を発する以外に、ソースファイルが改行で終了しているかどうかに応じて、ソースファイルを異なる方法で処理したコンパイラは今まで見たことがありませんが、それは本当だと思います。したがって、警告に驚かれるかもしれないことを除けば、知らないプログラマーを驚かせるものではありません。

したがって、真の移植性の問題(ほとんどが未指定または未定義ではなく実装に依存しますが、それは質問の精神に当てはまると思います)の場合:

  • char は必ずしも (un)signed ではありません。
  • int は 16 ビットから任意のサイズにすることができます。
  • float は必ずしも IEEE 形式または準拠であるとは限りません。
  • 整数型は必ずしも 2 の補数であるとは限らず、整数演算オーバーフローは未定義の動作を引き起こします (最新のハードウェアはクラッシュしませんが、一部のコンパイラの最適化は、ハードウェアが行うことであっても、ラップアラウンドとは異なる動作になります。たとえばif (x+1 < x)、常に false として最適化される場合があります。x型が署名されている場合: GCC のオプションを参照し-fstrict-overflowてください)。
  • 「/」、「。」#include の ".." には定義された意味がなく、異なるコンパイラによって異なる方法で処理される可能性があります (これは実際には異なり、問題が発生すると 1 日が台無しになります)。

動作が部分的に未定義/未指定であるため、開発したプラットフォームでさえ驚くことができる本当に深刻なもの:

  • POSIX スレッド化と ANSI メモリ モデル。メモリへの同時アクセスは、初心者が考えるほど明確に定義されていません。volatile は、初心者が考えることをしません。メモリ アクセスの順序は、初心者が考えるほど明確に定義されていません。アクセスは、特定の方向でメモリ バリアを越えて移動できます。メモリ キャッシュの一貫性は必要ありません。

  • コードのプロファイリングは、思ったほど簡単ではありません。テスト ループが効果がない場合、コンパイラはその一部または全部を削除できます。inline には定義された効果はありません。

そして、ニルスが通りすがりに言及したと思うように:

  • 厳密なエイリアシング規則に違反しています。
于 2008-09-19T01:18:39.307 に答える
21

何かへのポインタで何かを分割します。なんらかの理由でコンパイルされません...:-)

result = x/*y;
于 2008-09-19T00:44:25.240 に答える
21

私のお気に入りはこれです:

// what does this do?
x = x++;

いくつかのコメントに答えるために、それは標準による未定義の動作です。これを見て、コンパイラはハードドライブのフォーマットまで何でもできるようになります。たとえば、このコメントをここで参照してください。重要なのは、何らかの行動が合理的に期待できる可能性があることを確認できるということではありません。C ++標準とシーケンスポイントの定義方法により、このコード行は実際には未定義の動作です。

たとえば、x = 1上記の行の前にあった場合、その後の有効な結果はどうなりますか?誰かがそうあるべきだとコメントしました

xは1ずつ増加します

したがって、後でx==2が表示されるはずです。ただし、これは実際には当てはまりません。後でx == 1になるコンパイラ、またはx == 3になるコンパイラもあります。生成されたアセンブリを詳しく調べて、なぜそうなるのかを確認する必要がありますが、違いは原因です。根本的な問題に。x++基本的に、これは、コンパイラーが2つの代入ステートメントを好きな順序で評価できるため、最初のステートメントまたは最初のステートメントを実行できるためだと思いますx =

于 2008-09-19T00:33:22.240 に答える
11

私が遭遇した別の問題(これは定義されていますが、間違いなく予期しないものです)。

charは悪です。

  • コンパイラの感触に応じて、符号付きまたは符号なし
  • 8ビットとして必須ではありません
于 2008-09-19T07:58:06.523 に答える
9

引数に一致するように printf 書式指定子を修正した回数は数え切れません。不一致は未定義の動作です。

  • intいいえ、 (またはlong) を to に渡してはいけません%x- anunsigned intが必要です
  • unsigned intいいえ、 to を渡してはいけません%d-intが必要です
  • size_tいいえ、 to%uまたは%d- useを渡してはいけません%zu
  • %dいいえ、 orを使用してポインターを出力してはなりません%x- を使用%pしてにキャストしますvoid *
于 2013-08-15T19:31:17.837 に答える
8

関数プロトタイプが利用できない場合、コンパイラは、間違った数のパラメーター/間違ったパラメーター型で関数を呼び出していることを通知する必要はありません。

于 2008-09-19T02:40:37.893 に答える
5

clang 開発者は、すべての C プログラマーが読むべき投稿で、しばらく前にいくつかの優れた例を投稿しました。前に言及されていないいくつかの興味深いもの:

  • 符号付き整数のオーバーフロー - いいえ、符号付き変数をその最大値を超えてラップすることはできません。
  • NULL ポインターの逆参照 - はい、これは未定義であり、無視される可能性があります。リンクのパート 2 を参照してください。
于 2012-02-01T22:18:51.200 に答える
2

ここにいる EE は、a>>-2 が少し難しいことを発見しました。

私はうなずき、それは自然なことではないと彼らに伝えました。

于 2008-09-19T02:44:23.097 に答える
1

変数を使用する前に、必ず変数を初期化してください。私がCを始めたばかりのとき、それは私に多くの頭痛の種を引き起こしました。

于 2008-09-19T00:32:23.570 に答える