31

正しいオペランドの型に応じて異なる結果が得られるという興味深いシナリオに直面しましたが、その理由がよくわかりません。

最小限のコードは次のとおりです。

#include <iostream>
#include <cstdint>

int main()
{
    uint16_t check = 0x8123U;

    uint64_t new_check = (check & 0xFFFF) << 16;

    std::cout << std::hex << new_check << std::endl;

    new_check = (check & 0xFFFFU) << 16;

    std::cout << std::hex << new_check << std::endl;

    return 0;
}

Linux 64 ビットで g++ (gcc バージョン 4.5.2) を使用してこのコードをコンパイルしました: g++ -std=c++0x -Wall example.cpp -o example

出力は次のとおりです。

ffffffff81230000

81230000

最初のケースでの出力の理由がよくわかりません。

ある時点で、一時的な計算結果のいずれかが符号付き 64 ビット値 ( )に昇格さint64_tれ、符号が拡張されるのはなぜですか?

最初に 16 ビット値が 16 ビット左にシフトされ、次に 64 ビット値に昇格された場合、どちらの場合も「0」の結果を受け入れます。checkコンパイラが最初にtoをプロモートしてからuint64_t他の操作を実行する場合、2 番目の出力も受け入れます。

しかし、どうして&0xFFFF ( int32_t) と 0xFFFFU ( uint32_t) を比較すると、これら 2 つの異なる出力になるのでしょうか?

4

7 に答える 7

23

これは確かに興味深いコーナー ケースです。uint16_tアーキテクチャで 32 ビットを使用する場合に符号なし型を使用するため、ここでのみ発生します。ìnt

以下は、C++14 のドラフト n4296 の第 5 式の抜粋です (私のものを強調してください)。

10 算術型または列挙型のオペランドを予期する多くの二項演算子は、変換を引き起こします ... このパターンは通常の算術変換と呼ばれ、次のように定義されます:
...
(10.5.3) — それ以外の場合、符号なし整数を持つオペランドの場合型のランクが他のオペランドの型のランク以上である場合、符号付き整数型のオペランドは、符号なし整数型のオペランドの型に変換されます。
(10.5.4) — それ以外の場合、符号付き整数型のオペランドの型が符号なし整数型のオペランドの型のすべての値を表すことができる場合、符号なし整数型のオペランドはオペランドの型に変換されます。符号付き整数型。

10.5.4 の場合:

  • uint16_tintは32ビットですが、わずか16ビットです
  • intのすべての値を表すことができますuint16_t

したがって、uint16_t check = 0x8123Uオペランドは符号付きに変換され0x8123、ビット単位の結果&は依然として 0x8123 です。

しかし、シフト(ビット単位なので表現レベルで発生します)により、結果は中間の符号なし0x81230000になり、intに変換されて負の値になります(技術的には実装で定義されていますが、この変換は一般的な使用法です)

5.8 シフト演算子 [expr.shift]
...
それ以外の場合、E1 が符号付きの型で負でない値を持ち、E1×2 E2が結果の型の対応する符号なしの型で表現できる場合、その値は結果に変換されますタイプ、結果の値です;...

4.7 整数変換 [conv.integral]
...
3 変換先の型が符号付きの場合、変換先の型で表現できる場合、値は変更されません。それ以外の場合、値は実装定義です。

(これは C++11 では真の未定義の動作であったことに注意してください...)

したがって、signed int 0x81230000 を に変換するuint64_tと、予想どおり 0xFFFFFFFF81230000 が得られます。

4.7 整数変換 [conv.integral]
...
2 変換先の型が符号なしの場合、結果の値は変換元の整数と一致する最小の符号なし整数です (モジュロ 2n で、n は符号なしの型を表すために使用されるビット数です)。

TL/DR: ここには未定義の動作はありません。結果の原因は、符号付き 32 ビット int から符号なし 64 ビット int への変換です。未定義の動作である唯一の部分は、符号オーバーフローを引き起こすシフトですが、すべての一般的な実装はこれを共有しており、C++14 標準で定義された実装です。

もちろん、第 2 オペランドを強制的に符号なしにすると、すべてが符号なしになり、明らかに正しい0x81230000結果が得られます。

[編集] MSalters によって説明されているように、シフトの結果はC++14 以降に定義された実装のみですが、実際には C++11 では未定義の動作でした。シフト演算子の段落は次のように述べています。

...
そうではなく、 E1 が符号付きの型で負でない値を持ち、 E1×2 E2結果の型で表現できる場合、それが結果の値になります。それ以外の場合、動作は undefinedです。

于 2016-08-03T08:18:51.310 に答える
10

見てみましょう

uint64_t new_check = (check & 0xFFFF) << 16;

ここで、0xFFFFは符号付き定数なので(check & 0xFFFF)、整数昇格の規則により符号付き整数が得られます。

あなたの場合、32ビットint型では、左シフト後のこの整数のMSbitは1であるため、64ビット符号なしへの拡張は符号拡張を行い、左側のビットを1で埋めます。同じ負の値を与える 2 の補数表現として解釈されます。

2 番目のケースでは、0xFFFFUは符号なしであるため、符号なし整数が得られ、左シフト演算子は期待どおりに機能します。

ツールチェーンが最も便利な機能である をサポートしている場合__PRETTY_FUNCTION__、コンパイラが式の型をどのように認識するかをすばやく判断できます。

#include <iostream>
#include <cstdint>

template<typename T>
void typecheck(T const& t)
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
    std::cout << t << '\n';
}
int main()
{
    uint16_t check = 0x8123U;

    typecheck(0xFFFF);
    typecheck(check & 0xFFFF);
    typecheck((check & 0xFFFF) << 16);

    typecheck(0xFFFFU);
    typecheck(check & 0xFFFFU);
    typecheck((check & 0xFFFFU) << 16);

    return 0;
}

出力

void typecheck(const T &) [T = int]
65535
void typecheck(const T &) [T = int]
33059
void typecheck(const T &) [T = int]
-2128412672
void typecheck(const T &) [T = unsigned int]
65535
void typecheck(const T &) [T = unsigned int]
33059
void typecheck(const T &) [T = unsigned int]
2166554624
于 2016-08-03T07:36:05.440 に答える
10

最初に理解しておくべきことはa&b、組み込み型のような二項演算子は、両側が同じ型である場合にのみ機能するということです。(ユーザー定義型とオーバーロードでは、何でも構いません)。これは、暗黙の変換によって実現される場合があります。

さて、あなたの場合、間違いなくそのような変換があります。なぜなら、&よりも小さい型を取る二項演算子がないからintです。両側は少なくともintサイズに変換されますが、正確な型は何ですか?

たまたま、GCCintでは実際に 32 ビットです。uint16_tのすべての値をとして表すことができるため、これは重要intです。オーバーフローはありません。

したがって、check & 0xFFFF単純なケースです。右側はすでにintで、左側は に昇格するintので、結果はint(0x8123)です。これはまったく問題ありません。

さて、次の作戦は0x8123 << 16。お使いのシステムintでは 32 ビットでありINT_MAX0x7FFF'FFFF. オーバーフローがない場合は に0x8123 << 16なります0x81230000が、明らかにそれよりも大きくINT_MAX、実際にはオーバーフローがあります。

C++11 の符号付き整数オーバーフローは Undefined Behaviorです。purple文字通り、出力を含む、またはまったく出力しない、すべての結果が正しいです。少なくとも数値は得られましたが、GCC はオーバーフローを不可避的に引き起こすコード パスを完全に排除することが知られています。

[編集] 新しい GCC バージョンは C++14 をサポートしており、この特定の形式のオーバーフローは実装定義になっています - Serge の回答を参照してください。

于 2016-08-03T07:55:35.477 に答える
2

0xFFFF符号付き整数です。したがって、&操作の後、32 ビットの符号付きの値が得られます。

#include <stdint.h>
#include <type_traits>

uint64_t foo(uint16_t a) {
  auto x = (a & 0xFFFF);
  static_assert(std::is_same<int32_t, decltype(x)>::value, "not an int32_t")
  static_assert(std::is_same<uint16_t, decltype(x)>::value, "not a uint16_t");
  return x;
}

http://ideone.com/tEQmbP

元の 16 ビットは左にシフトされ、上位ビットが設定された 32 ビット値 (0x80000000U) になるため、負の値になります。64 ビットの変換中に符号拡張が発生し、上位ワードに 1 が入力されます。

于 2016-08-03T07:51:25.360 に答える
1

お使いのプラットフォームには 32 ビットのint.

あなたのコードは正確に同等です

#include <iostream>
#include <cstdint>

int main()
{
    uint16_t check = 0x8123U;
    auto a1 = (check & 0xFFFF) << 16
    uint64_t new_check = a1;
    std::cout << std::hex << new_check << std::endl;

    auto a2 = (check & 0xFFFFU) << 16;
    new_check = a2;
    std::cout << std::hex << new_check << std::endl;
    return 0;
}

a1との型は何a2ですか?

  • の場合a2、結果は に昇格されunsigned intます。
  • さらに興味深いことに、a1結果は に昇格されint、 に拡大されると符号拡張されuint64_tます。

符号付きと符号なしの型の違いが明確になるように、10 進数で示した短いデモを次に示します。

#include <iostream>
#include <cstdint>

int main()
{
    uint16_t check = 0;
    std::cout << check
              << "  " << (int)(check + 0x80000000)
              << "  " << (uint64_t)(int)(check + 0x80000000) << std::endl;
    return 0;
}

私のシステム(32ビットもint)では、

0  -2147483648  18446744071562067968

プロモーションと符号拡張が発生する場所を示します。

于 2016-08-03T08:16:38.570 に答える
1

これは整数昇格の結果です。操作が行われる前に、オペランドが(そのアーキテクチャ&の) よりも「小さい」場合、コンパイラは両方のオペランドをにプロモートします。intintsigned int

これは、最初の式が (32 ビット アーキテクチャの場合) と同等になることを意味します。

// check is uint16_t, but it fits into int32_t.
// the constant is signed, so it's sign-extended into an int
((int32_t)check & (int32_t)0xFFFFFFFF)

一方、もう1つは次のように昇格された2番目のオペランドを持ちます。

// check is uint16_t, but it fits into int32_t.
// the constant is unsigned, so the upper 16 bits are zero
((int32_t)check & (int32_t)0x0000FFFFU)

に明示的にキャストcheckするとunsigned int、結果はどちらの場合も同じになります (unsigned * signed結果は になりますunsigned)。

((uint32_t)check & 0xFFFF) << 16

は次のようになります。

((uint32_t)check & 0xFFFFU) << 16
于 2016-08-03T07:29:10.770 に答える
0

& 演算には 2 つのオペランドがあります。1 つ目は unsigned short で、通常の昇格を経て int になります。2 番目は定数で、一方は int 型、もう一方は unsigned int 型です。したがって、& の結果は一方の場合は int になり、他方の場合は unsigned int になります。その値は左にシフトされ、符号ビットが設定された int または unsigned int になります。負の int を uint64_t にキャストすると、大きな負の整数が得られます。

もちろん、常に次のルールに従う必要があります。何かを実行して結果が理解できない場合は、それを実行しないでください。

于 2016-08-03T07:52:39.987 に答える