unsigned int 和 int的隐式转换

标签:
体育 |
分类: 编程语言C/C /PERL/Tcl |
https://www.securecoding.cert.org/confluence/display/seccode/INT02-C.+Understand+integer+conversion+rules
Integer Conversion Rules
In C and C++, there are many different integer types, and often times type conversions are needed when two different integer types are involved in the operations.
Integer Type Conversion Rules
-
narrow unsigned
to wide unsigned/signed: zero-extend, then bit pattern is treated as target type; -
narrow signed
to wide signed/unsigned: sign-extend, then bit pattern is treated as target type; -
same-width
conversion: nop, bit-pattern is treated as target type; -
wide
to narrow: truncate, bit-pattern is treated as target type.
Integer Conversion Rank
Each integer type has a rank (C++2011 4.13). Rank-wise (I skipped signed keyword. signed long long = long long):
-
long long = unsigned long long
> long = unsigned long > int = unsigned int > short = unsigned short > unsigned char = unsigned char
Note: if width(x)>width(y), then rank(x)>rank(y). However rank(x)>rank(y) does not necessarily mean width(x)>width(y).
For example, on Win32/64, rank(long)>rank(int), but width(long)=width(int)=32-bit.
Integer Promotion
Any integer that has rank lower than
signed/unsigned
Integer Conversion Rules
When two integers are involved in an operation, type conversion may be required (C++2011 5/9):
-
Perform
integer promotion if either operand is short or char; -
If both operands have the
same type: DONE -
If operands are
both signed (or both unsigned): Promote the lower rank to higher rank. DONE -
If rank(unsigned
x) >= rank(signed
y): Convert the signed y
to
unsigned x type. DONE -
If width(unsigned
x) < width(signed
y): Convert both to
signed y. DONE -
Else, convert both
unsigned x and signed y to unsigned y .DONE
A few comments:
-
1. Integer promotion is performed even if both operands are of the
same type. Example: char + char
–> int + int, result type is int. - 3. If operands are both signed or both unsigned, it just converts lower ranker to higher ranker to preserve data.
- The tricky part is when one is unsigned and the other signed. The examples below assume Win32.
-
-
4.
unsigned rank prevails (even higher rank is not necessarily wider). Example: unsigned long + int -> unsigned long +unsigned long -
5. Even if the signed rank is
higher,
signed still needs to be wider to take the lead. Example: unsigned int + long long -> long long + long long -
6. Otherwise, rank(signed)>rank(unsigned), but
width(signed)<=width(unsigned), practically,
rank(signed)>rank(unsigned) and width(signed)=width(unsigned),
the higher ranker wins the rank and the unsigned wins the
signedness. Example:
unsigned int +long -> unsigned long + unsigned long - The conclusion:
-
-
For
signedness (i.e., signed, unsigned), result is unsigned, unless width(signed)>width(unsigned) then the result overridden to signed. -
For
base integer part (i.e., rank part, such as long long, long, int, short, char), the higher ranker always wins.
-
For
-
4.
You can probably imply that an unsigned integer is the “preferred” natural form for computers. A lot of computers really sees no difference for the unsigned and signed integers in operations, like add, sub, inc, dec in X86. These instructions work the same way no matter the operands are signed or unsigned, thanks to the 2 complement representation of integers.
中文版本:
. 类型转换 请点评
如果有人问C语法规则中最复杂的是哪一部分,我一定会说是类型转换。从上面两节可以看出,有符号、无符号整数和浮点数加起来有那么多种类型,每两种类型之间都要定义一个转换规则,转换规则的数量自然很庞大,更何况由于各种体系结构对于整数和浮点数的实现很不相同,很多类型转换的情况都是C标准未做明确规定的阴暗角落。虽然我们写代码时不会故意去触碰这些阴暗角落,但有时候会不小心犯错,所以了解一些未明确规定的情况还是有必要的,可以在出错时更容易分析错误原因。本节分成几小节,首先介绍哪些情况下会发生类型转换,会把什么类型转成什么类型,然后介绍编译器如何处理这样的类型转换。
3.1. Integer Promotion 请点评
在一个表达式中,凡是可以使用int
或unsigned int
类型做右值的地方也都可以使用有符号或无符号的char
型、short
型和Bit-field。如果原始类型的取值范围都能用int
型表示,则其类型被提升为int
,如果原始类型的取值范围用int
型表示不了,则提升为unsigned
int
型,这称为Integer Promotion。做Integer
Promotion只影响上述几种类型的值,对其它类型无影响。C99规定Integer Promotion适用于以下几种情况:
1、如果一个函数的形参类型未知,例如使用了Old Style C风格的函数声明(详见第 2 节
“自定义函数”),或者函数的参数列表中有...,那么调用函数时要对相应的实参做Integer
Promotion,此外,相应的实参如果是float
型的也要被提升为double
型,这条规则称为Default Argument Promotion。我们知道printf
的参数列表中有...
,除了第一个形参之外,其它形参的类型都是未知的,比如有这样的代码:
char ch = 'A'; printf("%c", ch);
ch
要被提升为int
型之后再传给printf
。
2、算术运算中的类型转换。有符号或无符号的char
型、short
型和Bit-field在做算术运算之前首先要做Integer
Promotion,然后才能参与计算。例如:
unsigned char c1 = 255, c2 = 2; int n = c1 + c2;
计算表达式c1 + c2
的过程其实是先把c1
和c2
提升为int
型然后再相加(unsigned
char
的取值范围是0~255,完全可以用int
表示,所以提升为int
就可以了,不需要提升为unsigned
int
),整个表达式的值也是int
型,最后的结果是257。假如没有这个提升的过程,c1 +
c2
就溢出了,溢出会得到什么结果是Undefined,在大多数平台上会把进位截掉,得到的结果应该是1。
除了+号之外还有哪些运算符在计算之前需要做Integer Promotion呢?我们在下一小节先介绍Usual Arithmetic Conversion规则,然后再解答这个问题。
3.2. Usual Arithmetic Conversion 请点评
两个算术类型的操作数做算术运算,比如a +
b
,如果两边操作数的类型不同,编译器会自动做类型转换,使两边类型相同之后才做运算,这称为Usual Arithmetic
Conversion。转换规则如下:
-
如果有一边的类型是
long double
,则把另一边也转成long double
。 -
否则,如果有一边的类型是
double
,则把另一边也转成double
。 -
否则,如果有一边的类型是
float
,则把另一边也转成float
。 -
否则,两边应该都是整型,首先按上一小节讲过的规则对
a
和b
做Integer Promotion,然后如果类型仍不相同,则需要继续转换。首先我们规定char
、short
、int
、long
、long long
的转换级别(Integer Conversion Rank)一个比一个高,同一类型的有符号和无符号数具有相同的Rank。转换规则如下:-
如果两边都是有符号数,或者都是无符号数,那么较低Rank的类型转换成较高Rank的类型。例如
unsigned int
和unsigned long
做算术运算时都转成unsigned long
。 -
否则,如果一边是无符号数另一边是有符号数,无符号数的Rank不低于有符号数的Rank,则把有符号数转成另一边的无符号类型。例如
unsigned long
和int
做算术运算时都转成unsigned long
,unsigned long
和long
做算术运算时也都转成unsigned long
。 这里的规则就是说明在相同的rank的情况下,无符号数是高于有符号数的。 -
剩下的情况是:一边有符号另一边无符号,并且无符号数的Rank低于有符号数的Rank。这时又分为两种情况,如果这个有符号数类型能够覆盖这个无符号数类型的取值范围,则把无符号数转成另一边的有符号类型。例如遵循LP64的平台上
unsigned int
和long
在做算术运算时都转成long
。 -
否则,也就是这个有符号数类型不足以覆盖这个无符号数类型的取值范围,则把两边都转成有符号数的Rank对应的无符号类型。例如在遵循ILP32的平台上
unsigned int
和long
在做算术运算时都转成unsigned long
。
-
可见有符号和无符号整数的转换规则是十分复杂的,虽然这是有明确规定的,不属于阴暗角落,但为了程序的可读性不应该依赖这些规则来写代码。我讲这些规则,不是为了让你用,而是为了让你了解有符号数和无符号数混用会非常麻烦,从而避免触及这些规则,并且在程序出错时记得往这上面找原因。所以这些规则不需要牢记,但要知道有这么回事,以便在用到的时候能找到我书上的这一段。
到目前为止我们学过的+ - * / % > < >= <= == !=运算符都需要做Usual Arithmetic Conversion,因为都要求两边操作数的类型一致,在下一章会介绍几种新的运算符也需要做Usual Arithmetic Conversion。单目运算符+ - ~只有一个操作数,移位运算符<< >>两边的操作数类型不要求一致,这些运算不需要做Usual Arithmetic Conversion,但也需要做Integer Promotion,运算符~ << >>将在下一章介绍。
3.3. 由赋值产生的类型转换 请点评
如果赋值或初始化时等号两边的类型不相同,则编译器会把等号右边的类型转换成等号左边的类型再做赋值。例如int c = 3.14;
,编译器会把右边的double
型转成int
型再赋给变量c
。
我们知道,函数调用传参的过程相当于定义形参并且用实参对其做初始化,函数返回的过程相当于定义一个临时变量并且用return
的表达式对其做初始化,所以由赋值产生的类型转换也适用于这两种情况。例如一个函数的原型是int
foo(int, int);
,则调用foo(3.1,
4.2)
时会自动把两个double
型的实参转成int
型赋给形参,如果这个函数定义中有返回语句return 1.2;
,则返回值1.2
会自动转成int
型再返回。
在函数调用和返回过程中发生的类型转换往往容易被忽视,因为函数原型和函数调用并没有写在一起。例如char c = getchar();
,看到这一句往往会想当然地认为getchar
的返回值是char
型,而事实上getchar
的返回值是int
型,这样赋值会引起类型转换,可能产生Bug,我们在第 2.5 节
“以字节为单位的I/O函数”详细讨论这个问题。
3.4. 强制类型转换 请点评
以上三种情况通称为隐式类型转换(Implicit Conversion,或者叫Coercion),编译器根据它自己的一套规则将一种类型自动转换成另一种类型。除此之外,程序员也可以通过类型转换运算符(Cast
Operator)自己规定某个表达式要转换成何种类型,这称为显式类型转换(Explicit
Conversion)或强制类型转换(Type Cast)。例如计算表达式(double)3 + i
,首先将整数3强制转换成double
型(值为3.0),然后和整型变量i
相加,这时适用Usual Arithmetic
Conversion规则,首先把i
也转成double
型,然后两者相加,最后整个表达式也是double
型的。这里的(double)
就是一个类型转换运算符,这种运算符由一个类型名套()括号组成,属于单目运算符,后面的3是这个运算符的操作数。注意操作数的类型必须是标量类型,转换之后的类型必须是标量类型或者void
型。
3.5. 编译器如何处理类型转换 请点评
以上几小节介绍了哪些情况下会发生类型转换,并且明确了每种情况下会把什么类型转成什么类型,本节介绍编译器如何处理任意两种类型之间的转换。现在要把一个M位的类型(值为X)转换成一个N位的类型,所有可能的情况如下表所示。
表 15.3. 如何做类型转换
待转换的类型 | M > N的情况 | M == N的情况 | M < N的情况 |
---|---|---|---|
signed integer to signed integer | 如果X在目标类型的取值范围内则值不变,否则Implementation-defined | 值不变 | 值不变 |
unsigned integer to signed integer | 如果X在目标类型的取值范围内则值不变,否则Implementation-defined | 如果X在目标类型的取值范围内则值不变,否则Implementation-defined | 值不变 |
signed integer to unsigned integer | X % 2N | X % 2N | X % 2N |
unsigned integer to unsigned integer | X % 2N | 值不变 | 值不变 |
floating-point to signed or unsigned integer | Truncate toward Zero,如果X的整数部分超出目标类型的取值范围则Undefined | ||
signed or unsigned integer to floating-point | 如果X在目标类型的取值范围内则值不变,但有可能损失精度,如果X超出目标类型的取值范围则Undefined | ||
floating-point to floating-point | 如果X在目标类型的取值范围内则值不变,但有可能损失精度,如果X超出目标类型的取值范围则Undefined | 值不变 | 值不变 |
注意上表中的“X % 2N”,我想表达的意思是“把X加上或者减去2N的整数倍,使结果落入[0, 2N-1]的范围内”,当X是负数时运算结果也得是正数,即运算结果和除数同号而不是和被除数同号,这不同于C语言%运算的定义。写程序时不要故意用上表中的规则,尤其不要触碰Implementation-defined和Undefined的情况,但程序出错时可以借助上表分析错误原因。
下面举几个例子说明上表的用法。比如把double
型转换成short
型,对应表中的“floating-point to
signed or unsigned integer”,如果原值在(-32769.0,
32768.0)之间则截掉小数部分得到转换结果,否则产生溢出,结果是Undefined,例如对于short s = 32768.4;
这个语句gcc
会报警告。
比如把int
型转换成unsigned short
型,对应表中的“signed
integer to unsigned
integer”,如果原值是正的,则把它除以216取模,其实就是取它的低16位,如果原值是负的,则加上216的整数倍,使结果落在[0,
65535]之间。
比如把int
类型转换成short
类型,对应表中的“signed integer
to signed integer”,如果原值在[-32768,
32767]之间则值不变,否则产生溢出,结果是Implementation-defined,例如对于short s = -32769;
这个语句gcc
会报警告。
最后一个例子,把short
型转换成int
型,对应表中的“signed integer to
signed
integer”,转换之后应该值不变。那怎么维持值不变呢?是不是在高位补16个0就行了呢?如果原值是-1,十六进制表示就是ffff,要转成int
型的-1需要变成ffffffff,因此需要在高位补16个1而不是16个0。换句话说,要维持值不变,在高位补1还是补0取决于原来的符号位,这称为符号扩展(Sign
Extension)。
几个例子:
b.
否则,如果一边是无符号数另一边是有符号数,无符号数的Rank不低于有符号数的Rank,则把有符号数转成另一边的无符号类型。例如unsigned
long
和int
做算术运算时都转成unsigned long
,unsigned
long
和long
做算术运算时也都转成unsigned
long
。
下面的例子就说明了这个问题。
http://s14/mw690/002mfkyxgy6HrXhUwR78d&690int