cに署名されていない整数を追加します
-
27-10-2019 - |
質問
ここに2つの非常に簡単なプログラムがあります。私は同じ出力を得ることを期待しますが、私はしません。理由がわからない。最初の出力251。2番目の出力-5。なぜ251を理解できます。しかし、2番目のプログラムが私に-5を与える理由はわかりません。
プログラム1:
#include <stdio.h>
int main()
{
unsigned char a;
unsigned char b;
unsigned int c;
a = 0;
b= -5;
c = (a + b);
printf("c hex: %x\n", c);
printf("c dec: %d\n",c);
}
出力:
c hex: fb
c dec: 251
プログラム2:
#include <stdio.h>
int main()
{
unsigned char a;
unsigned char b;
unsigned int c;
a = 0;
b= 5;
c = (a - b);
printf("c hex: %x\n", c);
printf("c dec: %d\n",c);
}
出力:
c hex: fffffffb
c dec: -5
解決
ここには2つの別々の問題があります。 1つ目は、同じ操作のように見えるものに対して異なる六角値を取得しているという事実です。あなたが欠けているという根本的な事実はそれです char
sは昇進します int
s(現状 short
s)算術を行う。違いは次のとおりです。
a = 0 //0x00
b = -5 //0xfb
c = (int)a + (int)b
ここ、 a
に拡張されます 0x00000000
と b
に拡張されます 0x000000fb
(いいえ それはendです 署名なし char)。その後、追加が実行され、 0x000000fb
.
a = 0 //0x00
b = 5 //0x05
c = (int)a - (int)b
ここ、 a
に拡張されます 0x00000000
と b
に拡張されます 0x00000005
. 。次に、減算が実行され、 0xfffffffb
.
ソリューション?続ける char
sまたは int
s;それらを混ぜると、あなたが期待しないものを引き起こす可能性があります。
2番目の問題は、です unsigned int
として印刷されています -5
, 、明らかに署名された値。しかし、文字列では、あなたは言いました printf
署名されたintとして解釈された2番目の引数を印刷するために(それが何ですか "%d"
意味)。ここでのトリックはそれです printf
あなたが渡した変数の種類が何であるかを知りません。それは、文字列がそれを言う方法でそれらを解釈するだけです。これが私たちが言う例です printf
intとしてポインターを印刷するには:
int main()
{
int a = 0;
int *p = &a;
printf("%d\n", p);
}
このプログラムを実行すると、毎回異なる値が表示されます。 a
, 、ベース10に変換されます。この種のことは警告を引き起こすことに注意してください。コンパイラーが提供するすべての警告を読み取り、意図していることを完全に確認している場合にのみ無視する必要があります。
他のヒント
最初のプログラムでは、 b=-5;
251を割り当てます b
. 。 (署名されていないタイプへの変換は、常に値Modulo 1と宛先タイプの最大値を削減します。)
2番目のプログラムでは、 b=5;
5に5を割り当てます b
, 、 それから c = (a - b);
減算0-5を実行します タイプとして int
デフォルトのプロモーションのために - 簡単に言えば、 int
「タイプは常に宣伝されています int
算術演算子およびビットワイズ演算子のオペランドとして使用される前。
編集: 私が見逃したことの1つ:それ以来 c
タイプがあります unsigned int
, 、2番目のプログラムの結果-5はに変換されます unsigned int
割り当ての場合 c
実行され、結果として UINT_MAX-4
. 。これはあなたが見るものです %x
指定器に printf
. 。印刷するとき c
と %d
, 、未定義の動作が得られます %d
(署名)を期待する int
議論とあなたは渡しました unsigned int
平野で表現できない値を持つ議論(署名) int
.
フォーマット仕様を使用しています %d
. 。それは議論を署名された小数点以下として扱います(基本的に int
).
最初のプログラムから251を取得します (unsigned char)-5
251では、署名された小数桁のように印刷します。それは1の代わりに4バイトに昇格し、それらのビットは 0
, 、だから数は次のように見えます 0000...251
(どこ 251
バイナリです、私はそれを変換しませんでした)。
2番目のプログラムから-5を取得します (unsigned int)-5
いくつかの大きな値ですが、にキャストされます int
, 、 これは -5
. 。あなたが使用する方法のために、それはintのように扱われます printf
.
フォーマット仕様を使用します %ud
署名されていない小数値を印刷します。
あなたが見ているのは結果です 基礎となるマシンが数字を表す方法 C標準が署名されていないタイプの変換に署名された方法(算術用)と、基礎となるマシンが数値を表す方法(最後の未定義の動作の結果)を表す方法。
私が元々私の応答を書いたとき、私はC標準が署名された値を符号なしの値に変換する方法を明示的に定義しなかったと想定していました。 標準は、署名された値をどのように表現するか、または符号付きタイプの範囲の外側にあるときに署名された値に署名された値に変換する方法を定義していません.
ただし、標準は、否定的な署名から正の符号なしの値に変換するときにそれを明示的に定義することがわかります。整数の場合、ネガティブサイン値xはuint_max+1-xに変換されます。
だからあなたが言うとき:
unsigned char a;
unsigned char b;
unsigned int c;
a = 0;
b = -5;
c = a + b;
-5は、C標準を使用して、-5がunsignedの値UCHAR_MAX-5+1(255-5+1)に変換されるため、251になります。その変換の後、追加が行われることです。 A + Bは0 + 251と同じになり、Cに保存されます。しかし、あなたが言うとき:
unsigned char a;
unsigned char b;
unsigned int c;
a = 0;
b = 5;
c = (a-b);
printf("c dec: %d\n", c);
この場合、aとbはcと一致するように符号なしのINTに昇格するため、値は0と5のままです。ただし、署名されていない整数数学で0-5は、UINT_MAX+1-5になるように定義されているアンダーフローエラーにつながります。これがプロモーションの前に発生した場合、値はuchar_max+1-5(つまり、再び251)になります。
ただし、出力に-5が印刷されていると見られる理由は、符号なしの整数UINT_MAX -4と-5が、-5と251がシングルバイトデータタイプを使用するのと同じように、まったく同じバイナリ表現を持っているという事実の組み合わせです。 「%d」をフォーマット文字列として使用した場合、cの値を署名されていない整数の代わりに署名された整数として解釈するようにprintfに指示したという事実。
無効な値から無効な値の署名された値への変換が定義されていないため、結果は実装固有になります。あなたの場合、基礎となるマシンは署名された値に対して2つの補数を使用するため、符号なしの値UINT_MAX -4が署名された値-5になります。
署名されていないINTと署名付きINTの両方が251を表すことができるため、これが最初のプログラムでは発生しない唯一の理由は251を表すため、2つの間の変換は明確に定義されており、「%d」または「%u」を使用することは問題ではありません。ただし、2番目のプログラムでは、UINT_MAX-4の値が署名されたINTの範囲外に出たため、未定義の動作をもたらし、実装固有になります。
ボンネットの下で何が起こっているのか
あなたが何が起こっていると思うか、または実際に何が起こっているかで何が起こるべきかを再確認するのは常に良いことです。そのため、今すぐコンパイラからのアセンブリ言語出力を見て、何が起こっているのかを正確に確認しましょう。これが最初のプログラムの意味のある部分です:
mov BYTE PTR [rbp-1], 0 ; a becomes 0
mov BYTE PTR [rbp-2], -5 ; b becomes -5, which as an unsigned char is also 251
movzx edx, BYTE PTR [rbp-1] ; promote a by zero-extending to an unsigned int, which is now 0
movzx eax, BYTE PTR [rbp-2] ; promote b by zero-extending to an unsigned int which is now 251
add eax, edx ; add a and b, that is, 0 and 251
バイトBに-5の署名値を保存しますが、コンパイラがそれを宣伝すると、数を拡張することでそれを促進することに注意してください。つまり、11111011が署名された値の代わりに表す署名されていない値として解釈されていることを意味します。その後、昇格した値が一緒に加えられてcになります。これは、C標準が署名されていないコンバージョンに署名された署名を定義する理由でもあります。これは、署名された値に2つの補数を使用するアーキテクチャ上のコンバージョンを簡単に実装できます。
今プログラム2で:
mov BYTE PTR [rbp-1], 0 ; a = 0
mov BYTE PTR [rbp-2], 5 ; b = 5
movzx edx, BYTE PTR [rbp-1] ; a is promoted to 32-bit integer with value 0
movzx eax, BYTE PTR [rbp-2] ; b is promoted to a 32-bit integer with value 5
mov ecx, edx
sub ecx, eax ; a - b is now done as 32-bit integers resulting in -5, which is '4294967291' when interpreted as unsigned
AとBは算術の前に再び宣伝されていることがわかります。そのため、2つの署名されていないINTを差し引くことになります。したがって、2つの補完フォームを使用しているマシンのために、署名付きまたは署名されていない減算として解釈するかどうかにかかわらず、結果は追加の変換なしでC標準と一致します。
負の数を署名していない変数に割り当てることは、基本的にルールを破ります。あなたがしていることは、負の数を大きな正の数に変換することです。技術的には、コンバージョンがあるプロセッサから別のプロセッサへと同じであることを保証することさえありません。1の補数システム(まだ存在している場合)では、異なる値が得られるでしょう。
だからあなたはあなたが得るものを手に入れます。署名された代数がまだ適用されることを期待することはできません。