加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

unsigned int 和 int的隐式转换

(2014-03-20 09:49:47)
标签:

体育

分类: 编程语言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 int (i.e, shortchar) is first converted to int or unsigned int before any consideration in an expression_r(C++2011 4.5). This can be understood as, signed/unsigned short/char are only used as storage; the calculation of them is always at least on signed/unsigned int. Signed/unsigned int is the most “natural” width for the specific computer architecture.

Integer Conversion Rules

When two integers are involved in an operation, type conversion may be required (C++2011 5/9):

  1. Perform integer promotion if either operand is short or char;
  2. If both operands have the same typeDONE
  3. If operands are both signed (or both unsigned): Promote the lower rank to higher rank. DONE
  4. If rank(unsigned x) >= rank(signed y): Convert the signed y to unsigned x type. DONE
  5. If width(unsigned x) < width(signed y): Convert both to signed yDONE
  6. Else, convert both unsigned x and signed y to unsigned yDONE

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.

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.

中文版本: http://learn.akae.cn/media/ch15s03.html

 

. 类型转换 请点评

如果有人问C语法规则中最复杂的是哪一部分,我一定会说是类型转换。从上面两节可以看出,有符号、无符号整数和浮点数加起来有那么多种类型,每两种类型之间都要定义一个转换规则,转换规则的数量自然很庞大,更何况由于各种体系结构对于整数和浮点数的实现很不相同,很多类型转换的情况都是C标准未做明确规定的阴暗角落。虽然我们写代码时不会故意去触碰这些阴暗角落,但有时候会不小心犯错,所以了解一些未明确规定的情况还是有必要的,可以在出错时更容易分析错误原因。本节分成几小节,首先介绍哪些情况下会发生类型转换,会把什么类型转成什么类型,然后介绍编译器如何处理这样的类型转换。

3.1. Integer Promotion 请点评

在一个表达式中,凡是可以使用intunsigned 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的过程其实是先把c1c2提升为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。转换规则如下:

  1. 如果有一边的类型是long double,则把另一边也转成long double

  2. 否则,如果有一边的类型是double,则把另一边也转成double

  3. 否则,如果有一边的类型是float,则把另一边也转成float

  4. 否则,两边应该都是整型,首先按上一小节讲过的规则对ab做Integer Promotion,然后如果类型仍不相同,则需要继续转换。首先我们规定charshortintlonglong long的转换级别(Integer Conversion Rank)一个比一个高,同一类型的有符号和无符号数具有相同的Rank。转换规则如下:

    1. 如果两边都是有符号数,或者都是无符号数,那么较低Rank的类型转换成较高Rank的类型。例如unsigned intunsigned long做算术运算时都转成unsigned long

    2. 否则,如果一边是无符号数另一边是有符号数,无符号数的Rank不低于有符号数的Rank,则把有符号数转成另一边的无符号类型。例如unsigned longint做算术运算时都转成unsigned longunsigned longlong做算术运算时也都转成unsigned long 这里的规则就是说明在相同的rank的情况下,无符号数是高于有符号数的。

    3. 剩下的情况是:一边有符号另一边无符号,并且无符号数的Rank低于有符号数的Rank。这时又分为两种情况,如果这个有符号数类型能够覆盖这个无符号数类型的取值范围,则把无符号数转成另一边的有符号类型。例如遵循LP64的平台上unsigned intlong在做算术运算时都转成long

    4. 否则,也就是这个有符号数类型不足以覆盖这个无符号数类型的取值范围,则把两边都转成有符号数的Rank对应的无符号类型。例如在遵循ILP32的平台上unsigned intlong在做算术运算时都转成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 longint做算术运算时都转成unsigned longunsigned longlong做算术运算时也都转成unsigned long。 这里的规则就是说明在相同的rank的情况下,无符号数是高于有符号数的。

下面的例子就说明了这个问题。

http://s14/mw690/002mfkyxgy6HrXhUwR78d&690int 和 int的隐式转换" TITLE="unsigned int 和 int的隐式转换" />

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有