web-dev-qa-db-ja.com

aが初期化されていない場合、a ^ aまたはa-aは未定義の動作ですか?

このプログラムを考えてみましょう:

#include <stdio.h>

int main(void)
{
    unsigned int a;
    printf("%u %u\n", a^a, a-a);
    return 0;
}

未定義の動作ですか?

表面的には、aは初期化されていない変数です。つまり、それは未定義の振る舞いを示しています。しかし、a^aa-aは、aのすべての値で0と同じです。少なくとも、そうだと思います。動作が明確に定義されていると主張する方法がある可能性はありますか?

74
David Heffernan

C11の場合:

  • aのアドレスが取得されない場合、6.3.2.1/2に従って明示的に定義されていません(以下に引用)
  • トラップ表現である可能性があります(アクセス時にUBが発生します)。 6.2.6.1/5:

特定のオブジェクト表現は、オブジェクトタイプの値を表す必要はありません。

符号なし整数はトラップ表現を持つことができます(たとえば、精度ビットが15でパリティビットが1の場合、aにアクセスするとパリティエラーが発生する可能性があります)。

6.2.4/6は、初期値が不確定であり、3.19.2での定義が不特定の値またはトラップ表現

さらに:C11 6.3.2.1/2で、Pascal Cuoqによって指摘されたように:

左辺値が、レジスタストレージクラスで宣言された可能性のある自動ストレージ期間のオブジェクトを指定し(アドレスが取得されなかった)、そのオブジェクトが初期化されていない(イニシャライザで宣言されておらず、使用前に割り当てが実行されていない)場合)、動作は未定義です。

これには文字タイプの例外がないため、この句は前の説明に取って代わるように見えます。トラップ表現が存在しない場合でも、xへのアクセスはすぐに未定義になります。この句 C11に追加されました 実際にレジスタのトラップ状態を持つItaniumCPUをサポートします。


トラップ表現のないシステム:ただし、_&x;_をスローすると、6.3.2.1/2の異論は適用されなくなり、トラップ表現がないことがわかっているシステムでは?次に、値は未指定の値です。 3.19.3のunspecified valueの定義は少し曖昧ですが、 DR 451 によって明確にされ、次のように結論付けられます。

  • 説明されている条件下で初期化されていない値は、その値を変更するように見えることがあります。
  • 不確定な値に対して実行される操作は、結果として不確定な値になります。
  • ライブラリ関数を不定の値で使用すると、未定義の動作を示します。
  • これらの回答は、トラップ表現を持たないすべてのタイプに適しています。

この解決策では、_int a; &a; int b = a - a;_の結果、bの値が不確定になります。

不確定な値がライブラリ関数に渡されない場合でも、未定義の動作(未定義の動作ではない)の領域にいることに注意してください。結果は奇妙な場合があります。 if ( j != j ) foo();はfooを呼び出すことができますが、悪魔は鼻腔に閉じ込められたままでなければなりません。

72
M.M

はい、それは未定義の動作です。

まず、初期化されていない変数は、「壊れた」(別名「トラップ」)表現を持つことができます。その表現にアクセスしようとすると、1回でも未定義の動作がトリガーされます。さらに、非トラッピングタイプのオブジェクト(unsigned charなど)でも、プラットフォームに依存する特別な状態(Itanium上のNaT-Not-A-Thing-など)を取得できます。これは、「不確定な値」の現れとして表示される場合があります。 。

次に、初期化されていない変数がstable値を持つことが保証されていません。同じ初期化されていない変数への2回の順次アクセスは、完全に異なる値を読み取ることができます。そのため、a - aの両方のアクセスが「成功」(トラップではない)であっても、次のことが保証されません。 a - aはゼロと評価されます。

32
AnT

オブジェクトに自動保存期間があり、そのアドレスが取得されない場合、オブジェクトを読み取ろうとすると、未定義の動作が発生します。このようなオブジェクトのアドレスを取得し、「unsigned char」タイプのポインタを使用してそのバイトを読み取ることは、「unsigned char」タイプの値を生成することが標準によって保証されていますが、すべてのコンパイラがその点で標準に準拠しているわけではありません。 。 ARM GCC 5.1、たとえば、次の場合:

  #include <stdint.h>
  #include <string.h>
  struct q { uint16_t x,y; };
  volatile uint16_t zz;
  int32_t foo(uint32_t x, uint32_t y)
  {
    struct q temp1,temp2;
    temp1.x = 3;
    if (y & 1)
      temp1.y = zz;
    memmove(&temp2,&temp1,sizeof temp1);
    return temp2.y;
  }

xが0〜65535の範囲外であっても、yがゼロの場合にxを返すコードを生成します。標準では、不確定値の符号なし文字読み取りはunsigned charの範囲内の値を生成することが保証されており、memmoveの動作は文字読み取りと書き込みのシーケンスと同等であると定義されています。したがって、temp2には、文字書き込みのシーケンスを介して格納できる値が必要ですが、gccは、memmoveを割り当てに置き換え、コードがtemp1とtemp2のアドレスを取得したという事実を無視することを決定しています。

コンパイラにそのタイプの任意の値を保持しているとコンパイラに強制する手段があると便利ですが、そのような値が同等に受け入れられる場合は役立ちますが、標準ではそうするためのクリーンな手段が指定されていません(保存機能するが、しばしば不必要に遅くなる特定の値を格納するため)。ビットのいくつかの組み合わせがすべてのコンパイラで機能することを信頼できるわけではないため、変数に表現可能な値を論理的に強制する必要がある操作でさえ。したがって、そのような変数については何の有用性も保証できません。

0
supercat