6

私はこの質問が何億回も質問され、答えられたように見えることを知っていますが、私自身の経験に答えを一致させることができないようです。

C規格では、加算のために「両方のオペランドは算術型でなければならない」(6.5.6.1)と規定されています。Arithemitc型は整数型と浮動型(6.2.5.18)をカバーし、最後に整数型はchar、short、int、long、long longであり、符号付き型と符号なし型(6.2.5.4と6.2.5.6)として存在します。通常の算術変換の規則によれば、「両方のオペランドが同じタイプの場合、それ以上の変換は必要ありません」。ここまでは順調ですね。

ここで「TheCBook」から例示されているように、汎整数拡張が適用される「[n]o演算はintよりも短い精度でCによって実行される」というのが私の理解です。私はこれを何度も見たようですが、標準ではこれへの言及を見つけることができません。

unsigned charは算術型であり、通常の算術変換の規則では、同じ型のオペランドは変換を必要としないと規定されているので、なぜ汎整数拡張が必要な​​のでしょうか。

2つの異なるコンパイラを使用してこれをテストしました。私はcharの加算を行う簡単なプログラムを書きました:

unsigned char a = 1;
unsigned char b = 2;
unsigned char c = a + b;

ターゲットプラットフォームは、8ビットアーキテクチャを使用するAtmelMega8uCです。したがって、オペランドを汎整数拡張の対象にする必要がある場合は、整数加算では2つのレジスタを使用する必要があります。

最適化を行わず、厳密なANSI C移植性オプションを有効にして、imagecraft avrコンパイラを使用してこれをコンパイルすると、次のアセンブリコードが生成されます。

mov R16, R20
add R16, R18

avr-gccの使用(gccの-strictに似たANSIスイッチを認識していません):

$ avr-gcc -O0 -mmcu=atmega8 -S -c main.c

結果のアセンブリ:

ldd r25,Y+1
ldd r24,Y+2
add r24,r25
std Y+3,r24

どちらの場合も、結果のコードは1バイトで動作します。ビットごとに同様の結果が得られます| および&および論理|| と &&。これは、標準が汎整数拡張なしで文字タイプの算術演算を許可することを意味しますか、それとも単にこれらのコンパイラが標準に準拠していないことを意味しますか?


追加:

結果が格納されているタイプによって異なります。上記の例は、結果がcharに格納されている場合にのみ当てはまり、加算の結果には依存しません。aを0xFFに設定し、bを1に設定すると、まったく同じアセンブリコードが生成されます。

cのタイプがunsignedintに変更された場合、結果のアセンブリは次のようになります。

mov R2,R20
clr R3
mov R16,R18 
clr R17
add R16,R2 
adc R17,R3 

結果を1バイトに保持できる場合、つまりa=1およびb=2の場合でも。

4

5 に答える 5

6

C 2011(n1570)6.3.1.8(「通常の算術変換」)1は、タイプが同じであるかどうかを検討する前に整数プロモーションが実行されることを示しています。

それ以外の場合、整数拡張は両方のオペランドで実行されます。次に、プロモートされたオペランドに次のルールが適用されます。

両方のオペランドのタイプが同じである場合、それ以上の変換は必要ありません…</ p>

したがって、C抽象マシンでは、算術演算を実行する前にunsigned char値をにプロモートする必要があります。(とが同じサイズのintパーバースマシンには例外があります。この場合、値はではなくに昇格されます。これは難解であり、通常の状況では考慮する必要はありません。)unsigned charintunsigned charunsigned intint

実際のマシンでは、抽象マシンで実行した場合と同じ結果が得られるように操作を実行する必要があります。結果のみが重要であるため、実際の中間操作は抽象マシンと正確に一致する必要はありません。

2つの値の合計がオブジェクトにunsigned char割り当てられると、合計はに変換​​されます。この変換は、基本的に、に収まるビットを超えるビットを破棄します。unsigned charunsigned charunsigned char

これは、Cの実装がこれを行うかどうかにかかわらず同じ結果を取得することを意味します。

  • 値をに変換しますint
  • 算術演算で値を追加しますint
  • 結果をに変換しunsigned charます。

またはこれ:

  • 算術演算で値を追加しますunsigned char

結果は同じであるため、Cの実装ではどちらの方法を使用してもかまいません。

比較のために、代わりに次のステートメントを検討できますint c = a + b;。また、コンパイラがとの値を認識していないaとしbます。この場合、算術演算を使用して加算を行うと、値を算術演算に変換して使用unsigned charする場合とは異なる結果が得られる可能性があります。たとえば、が250で200の場合、値としての合計は194(250 + 200%256)ですが、算術演算での合計は450です。違いがあるため、C実装では、正しい合計を取得する命令を使用する必要があります。 450。intintabunsigned charint

(コンパイラーがの値を知っていて、aまたはbそうでなければ合計がに適合することを証明できる場合unsigned char、コンパイラーは再びunsigned char算術を使用できます。)

于 2012-10-11T14:22:40.403 に答える
4

C99の関連部分は次のとおりです。

6.3.1算術オペランド
6.3.1.1ブール、文字、および整数
1すべての整数型には、次のように定義された整数変換ランクがあります
。...
2 intまたはunsignedintを使用できる場合は常に、式で次を使用できます
。整数変換ランクがintおよびunsignedintのランクよりも小さい整数型のオブジェクトまたは式。
— _Bool、int、signed int、またはunsignedint型のビットフィールド。
intが元の型のすべての値を表すことができる場合、値はintに変換されます。それ以外の場合は、unsignedintに変換されます。これらは整数プロモーションと呼ばれます。他のすべてのタイプは、整数プロモーションによって変更されません。

charあいまいであることに同意しますが、これは、さまざまな種類のorshortまたは_Booltointまたはのwrt変換を見つけることができる最も近いものですunsigned int

同じソースから:

5.1.2.3プログラム
の実行抽象マシンでは、すべての式がセマンティクスで指定されたとおりに評価されます。実際の実装では、その値が使用されておらず、必要な副作用(関数の呼び出しや揮発性オブジェクトへのアクセスによって引き起こされるものを含む)が発生していないと推測できる場合は、式の一部を評価する必要はありません。
...
10例2フラグメント
charc1、c2を実行する場合。
/ * ... * /
c1 = c1 + c2;
「整数拡張」では、抽象マシンが各変数の値をintサイズに拡張してから、2つのintを加算し、合計を切り捨てる必要があります。2つの文字の追加をオーバーフローなしで実行できる場合、またはオーバーフローをサイレントにラップして正しい結果を生成できる場合、実際の実行では同じ結果を生成するだけで済み、プロモーションは省略される可能性があります。

于 2012-10-11T14:53:23.020 に答える
2

6.3.1.8(通常の算術変換、n1570)では、次のように読み取ることができます。

それ以外の場合、整数拡張は両方のオペランドで実行されます。次に、プロモートされたオペランドに次のルールが適用されます。

したがって、整数拡張は、整数型の通常の算術変換の一部です。

したがって、abstratcマシンでは、への変換を行う(unsigned) int必要があります。

しかし、「あたかも」のルールにより、抽象マシンを厳密に実装することによって動作を区別できない場合、実装は異なる動作をする可能性があります。

したがって、シングルバイトのみを使用した計算がに昇格する計算と同じ結果になることが保証されている場合int、実装はシングルバイト演算を使用できます。

于 2012-10-11T14:05:19.413 に答える
1

コンピュータがよりも小さいタイプで操作を実行できる場合はint、必ず標準でそれを防ぐことはできません。標準では、コンパイラーが使用できるオプションをできるだけ多く維持しようとし、最適な方法を選択するかどうかの決定はコンパイラーに任されていることに注意してください。

「Cはintより短い精度で算術演算を行わない」という表現も正しいです。細心の注意を払うと、算術演算が実際に。以上の精度で行われていることがわかりますint。ただし、これは、サンプルプログラムの操作をバイトで安全に実行し、同じ精度を得ることができるため、コンパイラが整数拡張を強制されることを意味するものではありません。

于 2012-10-11T14:03:27.617 に答える
0

ここに矛盾はないと思います。コンパイラーは、観察可能な結果が所定の方法に従うかのようである限り、特定の計算パスに従う義務はありません。

特に、あなたの場合、intへの昇格(たとえば、16ビット)を使用して計算を行う場合:aに昇格するintと同じ値になり、b同様になります。の値a + bは実際(a + b) mod 2^16にはですが、これをunsigned charに割り当てます。これは、上位8ビットを切り捨てています。これは、結果を取得するのと同じですmod 2^8((a + b) mod 2^16) mod 2^8 = (a + b) mod 2^8

整数拡張なしで計算すると(a + b) mod 2^8、結果はまったく同じになります。

于 2012-10-11T14:03:14.453 に答える