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
}

这运行良好,直到我们达到 65535 的 uint16 限制。稍后,另一组打印语句,我们发现它numberA + 1的值为65536,而numberB回绕回0。这未通过检查,并且未进行任何处理。

这让我很好奇,所以我整理了一个快速的 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 + 1被提升为uint32_t。这是C语言的意图吗?或者这是一些编译器/硬件的奇怪之处?

4

4 回答 4

20

所以看起来结果numberA + 1被提升为uint32_t

加法的操作数在加法发生之前被提升,int加法的结果与有效操作数 ( ) 的类型相同int

实际上,如果int在您的编译平台上是 32 位宽(意味着表示的类型uint16_t具有比 更低的“转换等级” int),则numberA + 1计算为整数提升规则 6.3.1.1 的一部分int之间的加法1和提升: numberAC11标准中的2:

在可以使用 int 或 unsigned int 的表达式中,可以使用以下内容:[…] 具有整数类型(int 或 unsigned int 除外)的对象或表达式,其整数转换等级小于或等于整数和无符号整数。

[…]

如果一个 int 可以表示原始类型 […] 的所有值,则该值将转换为一个 int

在您的情况下,unsigned short很可能uint16_t是在您的平台上定义的内容,其所有值都可以表示为 的元素int,因此该unsigned short值在算术运算中发生时numberA会被提升为。int

于 2015-04-23T08:41:29.843 回答
10

对于算术运算符,例如+,应用通常的算术转换

对于整数,这些转换的第一步称为整数提升,这会将任何类型小于的值提升int为 an int

其他步骤不适用于您的示例,因此为了简洁起见,我将省略它们。

In the expression numberA + 1, the integer promotions are applied. 1 is already an int so it remains unchanged. numberA has type uint16_t which is narrower than int on your system, so numberA gets promoted to int.

The result of adding two ints is another int, and 65535 + 1 gives 65536 since you have 32-bit ints.

So your first printf outputs this result.

In the line:

numberB = numberA + 1;

the above logic still applies to the + operator, this is equivalent to:

numberB = 65536;

Since numberB has an unsigned type, uint16_t specifically, 65536 is reduced (mod 65536) which gives 0.

Note that your last two printf statements cause undefined behaviour; you must use %u for printing unsigned int. To cope with different sizes of int, you can use "%" PRIu32 to get the format specifier for uint32_t.

于 2015-04-23T08:57:35.017 回答
4

When the C language was being developed, it was desirable to minimize the number of kinds of arithmetic compilers had to deal with. Thus, most math operators (e.g. addition) supported only int+int, long+long, and double+double. While the language could have been simplified by omitting int+int (promoting everything to long instead), arithmetic on long values generally takes 2-4 times as much code as arithmetic on int values; since most programs are dominated by arithmetic on int types, that would have been very costly. Promoting float to double, by contrast, will in many cases save code, because it means that only two functions are needed to support float: convert to double, and convert from double. All other floating-point arithmetic operations need only support one floating-point type, and since floating-point math is often done by calling library routines the cost of calling a routine to add two double values is often the same as the cost to call a routine to add two float values.

Unfortunately, the C language became widespread on a variety of platforms before anyone really figured out what 0xFFFF + 1 should mean, and by that time there were already some compilers where the expression yielded 65536 and some where it yielded zero. Consequently, writers of standards have endeavored to write them in a fashion that would allow compilers to keep on doing whatever they were doing, but which was rather unhelpful from the standpoint of anyone hoping to write portable code. Thus, on platforms where int is 32 bits, 0xFFFF+1 will yield 65536, and on platforms where int is 16 bits, it will yield zero. If on some platform int happened to be 17 bits, 0xFFFF+1 would authorize the compiler to negate the laws of time and causality [btw, I don't know if any 17-bit platforms, but there are some 32-bit platforms where uint16_t x=0xFFFF; uint16_t y=x*x; will cause the compiler to garble the behavior of code which precedes it].

于 2015-04-23T15:39:27.023 回答
0

中的文字1int即在您的情况下为 int32 类型,因此使用 int32 和 int16 的操作给出 int32 的结果。

要将numberA + 1语句的结果作为uint16_ttry 显式类型转换1,例如:numberA + (uint16_t)1

于 2015-04-23T08:41:11.807 回答