C (および C++) では、算術演算子の型は次のように決定されます。
両方のオペランドは、「通常の算術変換」を使用して同じ型に変換されます。
それが結果のタイプです。
算術型または列挙型のオペランドを期待する多くの二項演算子は、同様の方法で変換を行い、結果の型を生成します。目的は、結果の型でもある共通の型を生成することです。このパターンは、通常の算術変換と呼ばれます[注 1] [注 2]
他にルールはないので、2 つ以上の演算子を使用する式に特別なケースはありません。各操作は、構文に従って個別に入力されます。
オーバーフローの可能性を回避または削減するために、結果の型が自動的に拡張されることはありません。オペランドは両方とも「結果の型でもある」共通の型に変換されます。したがって、2 つint
の s を乗算すると、結果は になり、int
オーバーフローにより未定義の動作が発生します。[注3]
言語の構文は、完全な式をグループ化する方法を正確に定義しており、構文に準拠するには評価が必要です。構文ではグループ化が必要なため、式a + b + c
は式と同じ結果になる必要があります。(a + b) + c
コンパイラは、結果がすべての有効な入力に対して意味的に同一であることを示すことができる場合、適切と思われるように計算を再配置できます。ただし、演算子の結果の型を変更することはできません。は、通常の算術変換を と の型に適用し、その型との型に再度適用しa + b + c
た結果の型を持たなければなりません。【注4】a
b
c
通常の算術変換は、C 標準の §6.3.1.8 (「通常の算術変換」) と、C++ の §5 (式) の概要の段落 10 で詳しく説明されています。大まかに言えば、次のようになります。
両方のオペランドが浮動小数点の場合、両方のオペランドは 2 つの型のうち幅の広い方に変換されます。一方のオペランドが浮動小数点の場合、もう一方はその浮動小数点型に変換されます。
それ以外の場合、両方のオペランドが符号付き整数型である場合、それらは両方とも と の 2 つの型のうち最も広いものに変換されますint
。
それ以外の場合、両方のオペランドが少なくとも と同じ大きさの符号なし整数型である場合unsigned int
、それらは両方とも 2 つの型のうち幅の広い方に変換されます。
【注5】
ここでa * b * c * d
、a
、b
、c
およびd
がすべてint
であり、目的は を生成することsize_t
です。
構文的には、その式は と同等(((a * b) * c) * d)
であり、通常の算術変換はそれに応じて操作ごとに適用されます。キャスト ( ) で変換a
すると、括弧で囲まれたように変換が適用されます。したがって、オペランドと の結果はになり、したがって の結果も同様になります。つまり、すべてのオペランドが符号なしの値に変換され、すべての乗算が符号なしの乗算として実行されます。これは明確に定義されていますが、値のいずれかが負の場合はおそらく意味がありません。size_t
(size_t)a * b * c * d
(size_t)a * b
size_t
(size_t)a * b * c
(size_t)a * b * c * d
size_t
size_t
2 番目または 3 番目の乗算は a の容量を超える可能性がありますsize_t
が、size_t
は符号なしであるため、計算はモジュロ 2 Nで実行されます。 ここN
で、 は の値のビット数ですsize_t
。したがって、キャストはオーバーフローを回避するという意味では安全ではありませんが、少なくとも未定義の動作は回避します。
ノート
引用は C++ 標準の §5、パラグラフ 10 からのものです。C11 には複雑な算術型が含まれているため、C 標準の §6.3.1.8 にはもう少し複雑なバージョンがあります。整数 (および非複素数浮動小数点) オペランドの場合、C と C++ のセマンティクスは同じです。
シフト演算子は例外です。そのため、「多くの二項演算子」と表示されています。シフト演算子の結果の型は、右のオペランドの型に関係なく、正確に左のオペランドの (おそらく昇格された) 型です。すべてのビット演算子は整数に制限されているため、実数を含む「通常の算術変換」の部分はこれらの演算子には適用されません。
2 つの を乗算unsigned int
すると、結果は になりunsigned int
、計算はすべての値に対して定義されます。
結果の符号なし整数型で表現できない結果は、結果の型で表現できる最大値よりも 1 大きい数値を法として減らされるため、符号なしオペランドを含む計算はオーバーフローすることはありません。(C §6.2.5/9)
C と C++ の両方の標準は、この点で非常に明確であり、理解を深めるための例が含まれています。一般に、符号付き整数演算子も浮動小数点演算子も結合的ではないため、計算に符号なし整数演算のみが含まれる場合にのみ、計算を再グループ化および再配置できます。
整数演算の再グループ化が禁止されるケースの例は、C 標準の §5.1.2.3 の例 6 および C++ 標準の §1.9 の段落 9 として表示されます。(同じ例です。) 16 ビットの s を持つマシンがint
あり、符号付きオーバーフローがトラップになるとします。その場合、次のa = a + 32760 + b + 5;
ように書き換えることはできませんa = (a + b) + 32765;
。
a と b の値がそれぞれ -32754 と -15 の場合、a + b の合計はトラップを生成しますが、元の式は生成しません。
それらは単純で問題のないケースです。通常、他のものは避けるようにしてください。
a. 上記が発生する前に、いずれかのオペランドの型が より狭い場合int
、そのオペランドは または のいずれかに昇格されint
ますunsigned int
。int
通常、署名されていない場合でも、に昇格されます。int
型のすべての値を表すのに十分な幅がない場合にのみ、オペランドは に昇格されunsigned int
ます。たとえば、ほとんどのアーキテクチャでは、オペランドはではなくunsigned char
に昇格されます(とが同じ幅のアーキテクチャは可能ですが、一般的ではありません)。int
unsigned int
char
int
b. 最後に、一方の型が署名され、もう一方が署名されていない場合、それらは両方とも次のように変換されます。
少なくとも符号付きタイプと同じ幅の場合は、符号なしタイプ。(例: unsigned int
* int
=> unsigned int
)
unsigned 型のすべての値を保持するのに十分な幅がある場合は、signed型。(例: unsigned int
* long long
=>が より広いlong long
場合)long long
int
上記のケースのいずれも当てはまらない場合は、符号付きの型に対応する符号なしの型。