Implementation Defined
实现定义的行为是由编译器设计者决定采取何种行动,并写入实用手册。
如字符型变量 char 是有符号型还是无符号型,C标准是没有明确规定的,要由编译器明确规定,并写在编译器
文档中。
Unspecified
这种情况,往往是在编译时C标准给出多种处理选择,由编译器而定,不需要写在编译器文档中,也就是说同一
编译器的不同版本,编译之后会有不同结果。
比如函数调用的各个实参表达式按什么顺序求值是Unspecified的。
Undefined
这种情况,顾名思义,就是未定义,C标准没有给出定义,编译器很可能也没有定义,甚至没有出错处理,有很
多Undefined情况编译器是检查不出来的,最终导致运行出错,比如数组访问越界。
以上三种是C标准中没有明确规定的三种情况。统称为“未明确定义”。
整型
char 类型在C标准中占8个bit,其他类型有两种标准如下
类型 | ILP32 | LP64 |
---|---|---|
char | 8 | 8 |
short | 16 | 16 |
int | 32 | 32 |
long | 32 | 64 |
long long | 64 | 64 |
指针 | 32 | 64 |
ILP32意思是int( I ) long ( L ) point( P ) 位数是,通常32位计算机使用ILP32标准,64位计算机使用LP64标准。而且指针类型长度都和计算机的位数相同。
C语言的常量有整数常量、字符常量、枚举常量和浮点数常量四种,其实字符常量和枚举常量的类型都是int型,因此前三种常量的类型都属于整型。
八进制和十六进制常量
C语言中也可以用八进制和十六进制的整数常量。八进制整数常量以0开头,后面的数字只能是0~ 7,例如022,因此十进制的整数常量就不能以0开头了,否则无法和八进制区分。十六进制整数常量以0x或0X开头,后面的数字可以是0 ~ 9、a ~ f 和 A ~ F。转义序列以\或\x加八进制或十六进制数字表示,这种表示方式相当于把八进制和十六进制整数常量开头的0替换成\了。
整形常量的后缀
整数常量还可以在末尾加u或U表示“unsigned”,加l或L表示“long”,加ll或LL表示“long long”,例如0x1234U,98765ULL等。但事实上u、l、ll这几种后缀和上面讲的unsigned、long、long long关键字并不是一一对应的。
整数常量的类型
后缀 | 十进制常量 | 八进制或十六进制常量 |
---|---|---|
无 | int long int long long int | int unsigned int long int unsigned long int long long int unsigned long long int |
u或U | unsigned int unsigned long int unsigned long long int | unsigned int unsigned long int unsigned long long int |
l或L | long int long long int | long int unsigned long int long long int unsigned long long int |
既有U或u 又有l或L | unsigned long int unsigned long long int | unsigned long int unsigned long long int |
ll或LL | long long int unsigned long long int | long long int unsigned long long int |
既有U或u 又有ll或LL | unsigned long long int | unsigned long long int |
由上图中可以观察出,当后缀含有U或u时三种进制数的常量类型相同,而当后缀中不含u时八进制和十六进制会相比十进制多出unsigned类型。
下面说一下如何识别类型,给定一个整数常量,比如1234U,那么它应该属于“u或U”这一行的“十进制常量”这一列,这个表格单元中列了三种类型unsigned int、unsigned long int、unsigned long long int,从上到下找出第一个足够长的类型可以表示1234这个数,那么它就是这个整数常量的类型,如果int是32位的那么unsigned int就可以表示。
再比如0xffff0000,应该属于第一行“无”的第二列“八进制或十六进制常量”,这一列有六种类型int、unsigned int、long int、unsigned long int、long long int、unsigned long long int,第一个类型int表示不了0xffff0000这么大的数,我们写这个十六进制常量是要表示一个正数,而它的MSB(第31位)是1,如果按有符号int类型来解释就成了负数了,第二个类型unsigned int可以表示这个数,所以这个十六进制常量的类型应该算unsigned int。所以请注意,0x7fffffff和0xffff0000这两个常量虽然看起来差不多,但前者是int型,而后者是unsigned int型。
浮点型
浮点数和整形相同,对于占多少字节都是Implementation Defined的,有float、double、long double几种类型。
有的处理器有浮点处理单元(FPU,floating point unite)被称为硬浮点实现;而有的处理器没有没有FPU,只能做整数运算,通过整数运算模拟浮点数运算,这种方式称为软浮点处理。
大部分浮点是遵循IEEE 754,float占32位,double占64位,而long double类型往往比double精度更高。
在不同平台下分配不同的存储空间,
- 在x86平台上大多数编译器实现时是80位,这是因为x86的浮点运算单元精度是80位
- gcc实现是96位(12个字节),这是为了对齐到四字节边界
- 也有些编译器的double和long double精度相同,没有充分利用x86FPU的精度。
- 其它体系结构的浮点运算单元的精度不同,编译器实现也会不同,例如PowerPC上的long double型通常是128位。
其中对于浮点数的表示,
是可以使用科学计数法的,例如 314e-2,表示3.14,也可以使用E。同时浮点数也可以加后缀,如3.14f,.01L,也可以使用F或l,这两种分别对应float和long double,没有后缀的话则认为是double型。
类型转换
Integer Promotion
对于表达式来说,凡是可以用int,unsigned int 做右值的地方,都可以用char 、short、field-bit 来代替, 如果原始类型的取值范围可以用 int 表示则都升为int,如果不可以则升为 unsigned int ,这就称为 Integer Promotion。做这种转换只对这几种类型有影响,对其它类型无影响。
- 特别的对于函数参数列表中有
...
或使用了 Old C style 风格的C函数声明,调用参数时都会使用Integer Promotion ,除此之外,对于 float 类型将其升为 double 类型,这条规则称为Default Argument Promotion。 - 算数运算中的类型转换,对于有符号的和无符号的char 、short 都要先Integer Promotion 在进行运算。
Usual Arithmetic Conversion
两个算数做算术运算,当运算数类型不同时,编译器会自动做类型转换,使两边类型相同在做运算,这就时Usual Arithmetic Conversion,下面是转换规则:
- 如果有一边的类型是long double,则把另一边也转成long double。
- 否则,如果有一边的类型是double,则把另一边也转成double
- 否则,如果有一边的类型是float,则把另一边也转成float。
- 否则,两边应该都是整型,首先按上一小节讲过的规则对a和b做Integer Promotion,然后如果类型仍不相同,则需要继续转换。之后要对几种整形的等级进行排序,以此为
char
short
int
long
long long
顺序从低到高。 之后按以下规则进行转换。
1.如果都是有符号数或都为无符号数,则将低级(rank)的类型向更高的rank转化,例如signed long和 signed long long,则将前者转化为 signed long long。
2.如果一个为无符号数另一个为有符号数,同时无符号数的rank不比有符号的低时那么就将有符号型转化为和无符号类型相同的类型,例如int
和unsigned long
则将int
转化为unsigned long
。
3.最后就是当无符号类型的rank比有符号类型低的时候,这时又分为两种情况,主要取决于有符号类型和无符号类型的取值范围的覆盖情况。当有符号类型能够覆盖无符号类型时,那么就把无符号类型转化为有符号类型,例如在LP64平台上的unsigned int
和long
,就会把后者类型转化为前者。当有符号行不能覆盖无符号型时,将有符号型转化为无符号型的相同类型,例如IPL32平台中unsigned int
和long
此时将long
转化为unsigned int
对于+ - * / % > < >= <= == !=运算符都需要做Usual Arithmetic Conversion,而对于单目运算符,和移位运算符<< >>两边的操作数类型不要求一致,这些运算不需要做Usual Arithmetic Conversion,但也需要做Integer Promotion
由赋值产生的类型转换
如果赋值时等号左右类型不一致,则将右值转化为与左值相同数据类型在赋值,例如:
int pi = 3.14;
就会将double类型的3.14转化为3再复制给pi
同时对于函数实参的传递也是赋值过程,有可能会将实参的类型转换,还有函数的return有可能会将return的类型进行转化。
在函数调用和返回过程中发生的类型转换往往容易被忽视,因为函数原型和函数调用并没有写在一起。例如char c = getchar();,看到这一句往往会想当然地认为getchar的返回值是char型,而事实上getchar的返回值是int型,这样赋值会引起类型转换,可能产生Bug。
强制类型转换
以上三种称为隐式转换(Implicit Conversion),此类转换由编译器根据其自己的转换规则进行操作。除此之外我们还可以用转换运算符(Cast Operator)规定表达式要转换成何种类型,这称为显示转换。