28

次のプログラムを検討してください。

// http://ideone.com/4I0dT
#include <limits>
#include <iostream>

int main()
{
    int max = std::numeric_limits<int>::max();
    unsigned int one = 1;
    unsigned int result = max + one;
    std::cout << result;
}

// http://ideone.com/UBuFZ
#include <limits>
#include <iostream>

int main()
{
    unsigned int us = 42;
    int neg = -43;
    int result = us + neg;
    std::cout << result;
}

+ 演算子は、返す正しい型をどのように「認識」しますか? 一般的な規則は、すべての引数を最も幅の広い型に変換することですが、ここでは と の間に明確な「勝者」はintありませんunsigned int。最初のケースでは、 の結果が得られるため、 がunsigned intの結果として選択されている必要があります。2 番目のケースでは、 の結果が得られるため、 を選択しているに違いありません。しかし、一般的なケースでは、これがどのように決定可能かはわかりません。私が見ているこの未定義の動作ですか、それとも何か他のものですか?operator+2147483648int-1

4

3 に答える 3

40

これは、§5/9 で明示的に概説されています。

算術型または列挙型のオペランドを想定する多くの二項演算子は、同様の方法で変換を行い、結果の型を生成します。目的は、結果の型でもある共通の型を生成することです。このパターンは通常の算術変換と呼ばれ、次のように定義されます。

  • いずれかのオペランドが 型のlong double場合、もう一方は に変換されlong doubleます。
  • それ以外の場合、いずれかのオペランドがdoubleの場合、もう一方は に変換されdoubleます。
  • それ以外の場合、いずれかのオペランドがfloatの場合、もう一方は に変換されfloatます。
  • それ以外の場合、両方のオペランドで整数昇格が実行されます。
  • 次に、どちらかのオペランドがunsigned longもう一方の場合、 に変換されunsigned longます。
  • それ以外の場合、一方のオペランドが along intで、もう一方がunsigned intである場合、 along intが an のすべての値を表すことができる場合unsigned int、はunsigned inta に変換されlong intます。それ以外の場合は、両方のオペランドが に変換されunsigned long intます。
  • それ以外の場合、いずれかのオペランドがlongの場合、もう一方は に変換されlongます。
  • それ以外の場合、いずれかのオペランドがunsignedの場合、もう一方は に変換されunsignedます。

[: それ以外の場合、両方のオペランドが である場合のみ残りますint]

どちらのシナリオでも、の結果はoperator+ですunsigned。したがって、2 番目のシナリオは事実上次のようになります。

int result = static_cast<int>(us + static_cast<unsigned>(neg));

この場合、 の値はus + negで表現できないためint、 の値resultは実装定義です – §4.7/3:

宛先の型が符号付きの場合、宛先の型 (およびビット フィールド幅) で表現できる場合、値は変更されません。それ以外の場合、値は実装定義です。

于 2011-07-21T01:06:23.130 に答える
12

C が標準化される前は、コンパイラ間に違いがありました。「値を保存する」規則に従うものもあれば、「符号を保存する」規則に従うものもありました。符号保持とは、いずれかのオペランドが符号なしの場合、結果が符号なしであることを意味していました。これは単純でしたが、時には驚くべき結果が得られました (特に、負の数が符号なしに変換された場合)。

C は、より複雑な「値保持」ルールに基づいて標準化されました。値保存規則の下では、昇格は型の実際の範囲に依存する可能性があるため、異なるコンパイラで異なる結果を得ることができます。たとえば、ほとんどの MS-DOS コンパイラでintは、 はどちらとも同じサイズですが、どちらshortlongも異なります。現在の多くのシステムintでは、 は と同じサイズですが、どちらlongshortも異なります。値保持ルールを使用すると、昇格された型が 2 つの間で異なる可能性があります。

値保存ルールの基本的な考え方は、小さい方の型のすべての値を表すことができる場合、より大きな符号付きの型に昇格するということです。たとえば、 のすべての可能な値はとして表すことができるため、16 ビットunsigned shortは 32 ビット に昇格できます。より小さい型の値を保持するために必要な場合にのみ、型は符号なし型に昇格されます (たとえば、とが両方とも 16 ビットである場合、 aは のすべての可能な値を表すことができないため、 anは に昇格されます)。 )。signed intunsigned shortsigned intunsigned shortsigned intsigned intunsigned shortunsigned shortunsigned int

あなたが持っているように結果を割り当てると、とにかく結果は目的の型に変換されるので、ほとんどの場合、ほとんど違いはありません.少なくともほとんどの典型的なケースでは、ビットを結果にコピーするだけです.それを署名済みと解釈するか、署名なしと解釈するかは、あなた次第です。

比較のように結果を代入しないと、かなり見苦しくなります例えば:

unsigned int a = 5;
signed int b = -5;

if (a > b)
    printf("Of course");
else
    printf("What!");

符号保存規則の下では、bは unsigned に昇格し、その過程で と等しくなるUINT_MAX - 4ため、「What!」の脚ifが取られます。値保存ルールを使用すると、このような奇妙な結果を生成することもできintますが、1) は主にと同じサイズの DOS ライクなシステムでshort、2) いずれにせよそれを行うのは一般的に困難です。

于 2011-07-21T01:24:45.753 に答える
2

結果を入れるタイプを選択するか、少なくとも cout が出力中にそのタイプを尊重します。

確かに覚えていませんが、C++ コンパイラは両方に対して同じ算術コードを生成すると思います。符号を気にするのは比較と出力だけです。

于 2011-07-21T01:04:07.437 に答える