7

これらの関数はすべて、私のマシンで期待される結果をもたらします。それらはすべて他のプラットフォームで動作しますか?

より具体的には、xが1の補数マシンでビット表現0xffffffffを持っている場合、または符号付きマグニチュードマシンで0x80000000を持っている場合、標準は(符号なし)xの表現について何と言っていますか?

また、v2、v2a、v3、v4での(符号なしの)キャストは冗長だと思います。これは正しいです?

sizeof(int)=4およびCHAR_BIT=8と仮定します

int logicalrightshift_v1 (int x, int n) {

    return (unsigned)x >> n;
}

int logicalrightshift_v2 (int x, int n) {

    int msb = 0x4000000 << 1;
    return ((x & 0x7fffffff) >> n) | (x & msb ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v2a (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (x & (unsigned)0x80000000 ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v3 (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (x < 0 ? (unsigned)0x80000000 >> n : 0);
}

int logicalrightshift_v4 (int x, int n) {

    return ((x & 0x7fffffff) >> n) | (((unsigned)x & 0x80000000) >> n);
}

int logicalrightshift_v5 (int x, int n) {

    unsigned y;
    *(int *)&y = x;
    y >>= n;
    *(unsigned *)&x = y;
    return x;
}

int logicalrightshift_v6 (int x, int n) {

    unsigned y;
    memcpy (&y, &x, sizeof (x));
    y >>= n;
    memcpy (&x, &y, sizeof (x));
    return x;
}
4

2 に答える 2

10

xのビット表現が1の補数マシンでは0xffffffff、符号付きマグニチュードマシンでは0x80000000の場合、標準は(符号なし)xの表現について何と言っていますか?

への変換は、表現ではなくunsignedで指定されます。に変換すると、常に取得されます(したがって、32ビットの場合は常に取得されます)。これは、実装で使用される符号付き数値の表現に関係なく発生します。-1unsignedUINT_MAXunsigned4294967295

同様に、に変換する-0unsigned常にが得られ0ます。 -0数値的には0に等しい。

負のゼロをサポートするために、1の補数または符号の大きさの実装は必要ないことに注意してください。そうでない場合は、そのような表現にアクセスすると、プログラムの動作が未定義になります。

関数を1つずつ確認します。

int logicalrightshift_v1(int x, int n)
{
    return (unsigned)x >> n;
}

の負の値に対するこの関数の結果は、xに依存し、がの範囲内にないUINT_MAX場合はさらに実装定義されます。たとえば、マシンが符号付き数値に使用する表現に関係なく、値を返します。(unsigned)x >> nintlogicalrightshift_v1(-1, 1)UINT_MAX / 2

int logicalrightshift_v2(int x, int n)
{
    int msb = 0x4000000 << 1;
    return ((x & 0x7fffffff) >> n) | (x & msb ? (unsigned)0x80000000 >> n : 0);
}

これに関するほとんどすべては、実装定義である可能性があります。符号ビットに1、値ビットにゼロを使用して値を作成しようとしていると仮定するとmsb、シフトを使用してこれを移植可能に行うことはできません-を使用できます~INT_MAXが、これは符号の大きさで未定義の動作をすることができます負のゼロを許可せず、2の補数マシンで実装定義の結果を与えることが許可されているマシン。

0x7fffffffおよびのタイプは0x80000000、さまざまなタイプの範囲に依存します。これは、この式の他の値がどのようにプロモートされるかに影響します。

int logicalrightshift_v2a(int x, int n)
{
    return ((x & 0x7fffffff) >> n) | (x & (unsigned)0x80000000 ? (unsigned)0x80000000 >> n : 0);
}

unsignedの範囲外の値を作成した場合int(たとえば、32ビットintの場合、values> 0x7fffffff)、returnステートメントでの暗黙的な変換により実装定義の値が生成されます。同じことがv3とv4にも当てはまります。

int logicalrightshift_v5(int x, int n)
{
    unsigned y;
    *(int *)&y = x;
    y >>= n;
    *(unsigned *)&x = y;
    return x;
}

intの表現の符号ビットがの表現の値ビットまたはパディングビットのどちらに対応するかが指定されていないため、これはまだ実装定義ですunsigned。パディングビットに対応する場合は、トラップ表現である可能性があります。その場合、動作は未定義です。

int logicalrightshift_v6(int x, int n)
{
    unsigned y;
    memcpy (&y, &x, sizeof (x));
    y >>= n;
    memcpy (&x, &y, sizeof (x));
    return x;
}

v5に適用されるのと同じコメントがこれに適用されます。

また、v2、v2a、v3、v4での(符号なしの)キャストは冗長だと思います。これは正しいです?

場合によります。16進定数として、その値が;の範囲内にある場合は0x80000000タイプになります。それ以外の場合、その値が;の範囲内にある場合。それ以外の場合、その値が;の範囲内にある場合。それ以外の場合(その値がの最小許容範囲内にあるため)。intintunsignedunsignedlonglongunsigned longunsigned long

符号なし型であることを確認したい場合は、定数の末尾Uに。を付け0x80000000Uます。


概要:

  1. より大きい数値INT_MAXを変換するとint、実装定義の結果が得られます(または、実際には、実装定義のシグナルを発生させることができます)。

  2. 範囲外の数値のへの変換は、のunsigned加算または減算を繰り返すことによって行われます。つまり、表現ではなく、数学的なUINT_MAX + 1に依存します。

  3. 移植性がないため、否定的なint表現を検査します(ただし、肯定的な表現は問題ありません)。unsignedint

  4. ビット単位の演算子を使用して負のゼロを生成し、結果の値を使用しようとすると、移植性がありません。

「論理シフト」が必要な場合は、どこでも符号なし型を使用する必要があります。符号付きタイプは、表現ではなく値が重要であるアルゴリズムを処理するために設計されています。

于 2011-10-28T06:38:13.637 に答える
2

標準に準拠している場合、これらのいずれもすべてのプラットフォームで同じであるとは限りません。

v5では、未定義の動作であるstrict-aliasingに違反しています。

v2〜v4では、実装定義である右シフトに署名しました。(詳細についてはコメントを参照してください)

v1では、符号なしキャストに署名しました。これは、数値が範囲外の場合に定義される実装です。

編集:

v6は、次の前提条件で実際に機能する可能性があります。

  • 「int」は2または1の補数です。
  • unsignedintはまったく同じサイズです(バイトとビットの両方で、密集しています)。
  • のエンディアンは。のエンディアンとunsigned一致しintます。
  • パディングとビットレイアウトは同じです:(詳細については、cafのコメントを参照してください)。
于 2011-10-28T05:47:33.683 に答える