【c语言总结】类型&转换
前言:
最近因为定位一个内核的bug,最后发现是因为三目运算未考虑不同类型的变量运算时会导致类型自动被转换而导致预期结果不一样的问题。在此基础上,将这方面的c语言知识再次进行剖析一下,让自己印象更深刻。
一、变量类型
1.1 变量类型大小
在这张表中,LP64,ILP64,LLP64是64位平台上的字长模型,ILP32和LP32是32位平台上的字长模型。
LP64: 指long和pointer是64位,
ILP64:指int,long,pointer是64位,
LLP64:指long long和pointer是64-bit的。
ILP32:指int,long和pointer是32位的,
LP32: 指long和pointer是32位的。
1.2 变量取值范围
二、转换规则
2.1 隐式转换
C在以下四种情况下会进行隐式转换:
算术运算式中,低类型能够转换为高类型。
赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。
函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。
2.2 算数运算的隐式转换
算数运算中,首先有如下类型转换规则:
字符必须先转换为整数(C语言规定字符类型数据和整型数据之间可以通用) 。
short型转换为int型(同属于整型) 。
float型数据在运算时一律转换为双精度(double)型,以提高运算精度(同属于实型) 。
当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。
当不同类型的数据进行操作时,应当首先将其转换成相同的数据类型,然后进行操作,转换规则是由低级向高级转换。转换规则如下图所示:
转换的基本原则:按数据长度增加的方向进行转换。
三、printf打印
无符号数可以以八进制、十进制和十六进制的形式输出。
严格来说,格式控制符和整数的符号是紧密相关的,具体就是:
%d :以十进制形式输出有符号数;
%u :以十进制形式输出无符号数;
%o :以八进制形式输出无符号数;
%x :以十六进制形式输出无符号数。
printf的原理:
当以有符号数的形式输出时,printf 会读取数字所占用的内存,并把最高位作为符号位,把剩下的内存作为数值位;
当以无符号数的形式输出时,printf 也会读取数字所占用的内存,并把所有的内存都作为数值位对待。
四、内存中存储方式
C语言规定,把内存的最高位作为符号位。符号也是数字的一部分,也要在内存中体现出来。符号只有正负两种情况,用1位(Bit)就足以表示;C语言规定,在符号位中,用 0 表示正数,用 1 表示负数。对于一个有符号的正数,它的符号位是0,当按照无符号数的形式读取时,符号位就变成了数值位,但是该位是0,而不是1,所以对数值不回产生影响。这就好比在一个数字前面加0,有多少个0都不会影响数字的值。
在计算机当中:
原码:正数的反码和原码相同。
负数:负数以原码的补码的形式表达的。
反码:负数的反码为该数的原码除符号位外各位取反。
补码:原码取反加1(除符号位外)
接下来以-2来举例说明:
十进制:2
二进制:0000 0010(一个字节)
接下来以4个字节为例进行说明:
接下来以一个c语言的变量定义和算数运算来举例说明以上的原理:
以上变量在内存中的存储方式如下所示:
当执行e = (d == 0) ? b : c三目运算后,有符号的b会自动转换成无符号的数进行计算,即将b原本作为符号位的最高位1当作了数值进行运算,此时b以十进制来看就是4294967294。
所以最终e = b,因为e是int类型,所以现在在内存中是这么存储的:
此时,最高位1是符号位。所以如果此时用%d去打印,则输出值则是-2。
如果此时e的类型是long类型,则此时会在左侧补32个0。具体如下所示:
此时符号位是最高位的0,而不是倒数第32位的1,此时用%ld去打印,则是4294967294。
其实如果int e类型,如果用%ld去打印(注:正常int类型是不能使用%ld来打印,这是非法的用法,此处只是举例说明),也是4294967294。因为%ld是32位的,所以此时会在左侧补32个0。
综上,在c语言中,当一个无符号数和一个有符号数进行比较运算时,有符号数会被隐含的转换成无符号数,并假设这两个数都是非负数,然后进行运算。
当把一个有符号数转换成无符号数时,其底层的二进制没有改变,仅仅是对其进行了不同的解释。