26

そのため、フィールドの問題があり、何日もデバッグした後、問題をこの特定のコードに絞り込みました。ここでは、while ループの処理が行われていませんでした。

// heavily redacted code
// numberA and numberB are both of uint16_t
// Important stuff happens in that while loop

while ( numberA + 1 == numberB )
{
    // some processing
}

numberA + 1uint16 の制限である 65535 に達するまで、これは問題なく実行されていました。後で別の一連の print ステートメントを実行したところ、値が であることが65536わかりましnumberB0。これはチェックに失敗し、処理は行われませんでした。

これが気になったので、これを確認するために簡単な C プログラム (GCC 4.9.2 でコンパイル) を作成しました。

#include <stdio.h>
#include <stdint.h>

int main()
{

    uint16_t numberA, numberB;
    numberA = 65535;
    numberB = numberA + 1;

    uint32_t numberC, numberD;
    numberC = 4294967295;
    numberD = numberC + 1;

    printf("numberA = %d\n", numberA + 1);
    printf("numberB = %d\n", numberB);

    printf("numberC = %d\n", numberC + 1);
    printf("numberD = %d\n", numberD);

    return 0;
}

結果は次のとおりです。

numberA = 65536
numberB = 0
numberC = 0
numberD = 0

そのため、 の結果がnumberA + 1uint32_t に昇格されたようです。これはC言語が意図したものですか?それとも、これはコンパイラ/ハードウェアの奇妙さですか?

4

4 に答える 4

10

などの算術演算子の+場合、通常の算術変換が適用されます。

整数の場合、これらの変換の最初のステップは整数昇格と呼ばれ、これにより、より小さな型の値が に昇格intされますint

他の手順はあなたの例には当てはまらないので、簡潔にするために省略します。

numberA + 1では、整数の昇格が適用されます。1はすでにint変更されていないため、変更されません。は、システムよりも狭いnumberA型を持っているため、に昇格します。uint16_tintnumberAint

2 つの s を追加した結果intは別のintになり、32 ビットの s があるため、65535 + 1が得られます。65536int

したがって、最初にprintfこの結果が出力されます。

行で:

numberB = numberA + 1;

上記のロジックは引き続き+演算子に適用されます。これは次と同等です。

numberB = 65536;

具体的にはnumberB、符号なしの型を持っているため、削減されます (mod 65536) 。uint16_t655360

最後の 2 つのprintfステートメントは未定義の動作を引き起こすことに注意してください。%u印刷に使用する必要がありますunsigned int。のさまざまなサイズに対処するためにint、 を使用"%" PRIu32して の書式指定子を取得できますuint32_t

于 2015-04-23T08:57:35.017 に答える
4

C 言語が開発されたとき、演算コンパイラが処理しなければならない種類の数を最小限に抑えることが望まれました。したがって、ほとんどの数学演算子 (加算など) は、int+int、long+long、および double+double のみをサポートしていました。int+int を省略して (long代わりにすべてをに昇格させて) 言語を単純化することもできますが、値の算術演算はlong通常、値の算術演算の 2 ~ 4 倍のコードを必要としintます。ほとんどのプログラムはint型の算術演算に支配されているため、非常にコストがかかります。対照的に、にプロモートfloatするdoubleと、多くの場合、コードを節約できます。これは、サポートに必要な関数が 2 つだけであることを意味するためですfloat: convert todoubleと convert fromdouble. 他のすべての浮動小数点算術演算は、1 つの浮動小数点型のみをサポートする必要があります。また、浮動小数点演算はライブラリ ルーチンを呼び出すことによって行われることが多いため、2 つのdouble値を加算するルーチンを呼び出すコストは、多くの場合、ルーチンを呼び出すコストと同じです。 2 つの値を追加しfloatます。

残念なことに、0xFFFF + 1 が何を意味するのかを誰もが実際に理解する前に、C 言語はさまざまなプラットフォームで広く普及しました。その時までに、式が 65536 を生成するコンパイラとゼロを生成するコンパイラがすでに存在していました。その結果、標準の作成者は、コンパイラが何をしていてもやり続けることができるような方法で標準を記述しようと努力してきましたが、移植可能なコードを書きたいと考えている人の観点からはかなり役に立ちませんでした。したがって、intが 32 ビットのプラットフォームでは 0xFFFF+1 は 65536 になり、intが 16 ビットのプラットフォームではゼロになります。あるプラットフォームの場合intたまたま 17 ビットだった場合、0xFFFF+1 はコンパイラが時間と因果関係の法則を否定することを許可します [ちなみに、17 ビット プラットフォームがあるかどうかはわかりませんがuint16_t x=0xFFFF; uint16_t y=x*x;、コンパイラがその前にあるコードの動作を台無しにする]。

于 2015-04-23T15:39:27.023 に答える