您的位置:首页 > 编程语言 > C语言/C++

C语言中的类型转换与数据的机器码存储

2017-01-19 15:24 323 查看

各种类型的表示范围

对于涉及到了混合着不同数据类型的表达式中的数据类型的转换问题。在总结转换问题之前,先说明一下32位机上的各种数据类型。

类型名
字节数
其他类型名
10进制表示范围(机器码)
char
1
signed char
-128—127(0X80-0X7F)
unsigned char
1
none
0-255(0X00-0XFF)
short
2
short int
signed short int
-32768—32767
(0X8000-0X7FFF)
unsigned short
2
unsigned short
0-65535
(0X0000-0XFFFF)
int
4
signed int
-2147483648—2147483647
(0X80000000-0X7FFFFFFF)
unsigend int
4
none
0—4294967295
(0X80000000-0XFFFFFFFF)
long
4
long int
signed long int
-2147483648—2147483647
(0X80000000-0X7FFFFFFF)
unsigned long
4
unsigned long int
0—4294967295
(0X00000000-0XFFFFFFFF)
long long
8
signed long long int
signed long long
long long int
0X8000000000000000-
0X7FFFFFFFFFFFFFFF
unsigned long long
8
unsigned long long int
0X0000000000000000-
0XFFFFFFFFFFFFFFFF
enum
4
none
same as int
float
4
none
3.4E +/- 38 (7 digits)
double
8
none
1.7E +/- 308 (15 digits)
说明一下:

1)在32位机上,int型和unsigned
int型都是32位的(4个字节);

2)enum会根据最大值来决定类型,一般来说为int型,如果超出int型所能表示的范围,则用比int型大的最小类型来表示(unsigned
int, long 或者unsigned long);

3)关于类型的大小。一般用所能表示的数据范围来比较类型的大小,如char型<unsigned
char型<short型,在表达式中,一般都是由小的类型向大的类型转换(强制类型转换除外)。

4)-128的补码表示是0X80,二进制写法是10000000,补码能直接参与加减运算,如-128+127=-1,用补码进制运算:10000000+01111111
= 11111111,即0XFF恰好是-1的补码。

关于类型转换

下面总结一下关于类型转换(仅限于算术表达式中整型的转换)的原则:

1)所有比int型小的数据类型(包括char、signed
char、unsigned char、short、signed
short、unsigned short)转换为int型。如果转换后的数据会超出int型所能表示的范围的话,则转换为unsigned
int型


2)bool型转化为int型时,false转化为0,true转换为1;反过来所有的整数类型转化为bool时,0转化为false,其它非零值都转为true

3)如果表达式中混有unsigned short和int型时,如果int型数据可以表示所有的unsigned
short型的话,则将unsigned short类型的数据转换为int型;否则unsigned
short类型及int型都转换为unsigned int类型。举个例子,在32位机上,int是32位,范围–2147483648——2147483647;unsigned
short是16位,范围是0——65535。这样int型足够表示unsigned
short类型的数据,因此在混有这两者的运算中,unsigned short类型数据被转换为int型;

4)unsigned int与long类型的转换规律同3,在32位机上,unsigned
int是32位,范围0
——4294967295,long也是32位,范围–2147483648
——2147483647,可见long类型不够表示所有的unsigned
int型,因此在混有unsigned int及long的表达式中,两者都被转换为unsigned
long,值得注意的是32位机上unsigned long也占用4字节空间;


5)如果表达式中既有int
又有unsigned int,则所有的int数据都被转化为unsigned
int类型


6)在计算机中,负数是以补码来存储的。

经典的测试代码
32位机上测试代码如下:

int main(void)
{
{ //(1)
unsigned char a = -1;
char b = a;
printf("a = %d,b = %d\n",a,b); // a = 255,b = -1
}
{ //(2)
unsigned short a = -1;
short b = a;
printf("a = %d,b = %d\n",a,b); //a =65535,b = -1
}
{ //(3)
unsigned int a = -1;
int b = a;
printf("a = %u,b = %d\n",a,b); //a =65535,b = -1
printf("a = %d,b = %d\n",a,b); //a = -1,b = -1
}
{ //(4)
signed char a = 0XE0;
unsigned int b = a;
unsigned char c = a;
printf("a = %d,b = %d,c = %d\n",a,b,c); //a = -32,b = -32,c = 224
printf("a = %d,b = %u,c = %d\n",a,b,c); //a = -32,b = 4294967264,c = 224
}
{ //(5)
unsigned int i=3;
printf("i*(-1) = %d\n",i*(-1)); //i*(-1) = -3
printf("i*(-1) = %u\n",i*(-1)); //i*(-1) = 4294967293
std::cout<<i*(-1)<<std::endl; //4294967293
}
{ //(6)
unsigned int A = 1;
unsigned int B = 3;
unsigned int C = A - B;
unsigned int D = 4;

if(A-B >= D) //4294967294 >= 4
printf("aaaaaaaaaaaaaaaaaa\n"); //aaaaaaaaaaaaaaaaaa

printf("C = %u\n",C); //C = 4294967294
printf("C = %d\n",C); //C = -2
printf("C = %X\n",C); //C = FFFFFFFE
}
{ //(7)
char a[1000] ;
int i ;
for(i=0; i<1000; i++)
{
a[i]= -1-i ;
}
printf("strlen(a) = %u\n",strlen(a)) ; //strlen(a) = 255
}
return 0;
}
代码分析:

对于(1),C语言中常量整型数-1的补码表示为0XFFFFFFFF。截取后面8位FF赋值给变量a(unsigned
char),此时a = 0XFF(a没有符号位,0XFF转换为十进制为255),又将0XFF,直接赋值给char
b,此时b = 0XFF(但是要注意,b是有符号的,0XFF是一个负数的补码表示,转换为十进制为整数-1)。

执行语句printf("a = %d,b = %d\n",a,b)的时候,要将 a和b的值先转换为int型:a没有符号所以转为int型为0x0000FF,b有符号转换为int型为0xFFFFFFFF。十进制整型数输出值为a
=255,b = -1

对于(2),截取后面16位FFFF赋值给变量a(unsigned
short)。此时a = 0XFFFF(a没有符号位,0XFFFF转换为十进制为65535),又将0XFFFF,直接赋值给short
b。 此时b = 0XFFFF(但是要注意,b是有符号的,0XFFFF是一个负数的补码表示,转换为十进制为整数-1)。

执行语句printf("a = %d,b = %d\n",a,b)的时候,要将 a和b的值先转换为int型:a没有符号所以转为int型为0x0000FFFF,b有符号转换为int型为0xFFFFFFFF。十进制整型数输出值为a
=65535,b = -1

对于(3),a在内存中值为0XFFFFFFFF,b的值为0XFFFFFFFF,都已经32位,a转换为int型的时候就是0XFFFFFFFF,所以输出都是-1。

对于(4),a赋值给b时,首先是signed
char转换为int,然后int转换成unsigned
int,所以最初是符号扩展,1字节的0XE0扩展成4字节的0XFFFFFFE0然后一个int赋值给了unsigned
int,都是32位,b在内存中就是0XFFFFFFE0。a以有符号整型的形式打印,0XFFFFFFE0是补码,原码是0X80000020,十进制表示就是-32,若以符号整型数的形式打印,0XFFFFFFE0是原码,十进制表示就是4294967264,c在内存中表示是0XE0,无符号数,以%d形式打印先转换为int型的时候就是0X000000E0,十进制表示是224。

对于(5),在表达式i*-1中,i是unsigned
int型,-1是int型(常量整数的类型等同于enum),-1必须转换为unsigned
int型,即0XFFFFFFFF,十进制的4294967295,然后再与i相乘,即4294967295*3,如果不考虑溢出的话,结果是12884901885,十六进制0X2FFFFFFFD,由于unsigned
int只能表示32位,因此结果是0XFFFFFFFD,即4294967293。

对于(6),该段测试了无符号数减法运算的溢出现象,两个无符号数相减,无论是否溢出,结果一定依然是一个无符号的正数。首先作纯算术运算得到1-3=-2,-2以int型的补码形式存储在内存中是0XFFFFFFFE,-2是int型,将其赋值给unsigned
int型的C时,必须转换成unsigned int型,都是32位,C在内存中就是0XFFFFFFFE,A-B
>= D中两边都是unsigned int型,0XFFFFFFFE以unsigned
int型读取是正数4294967294,远大于4。C以%d形式打印,即转换为int型,把C当作int型数据,0XFFFFFFFE是-2的补码,故打印出C
= -2。

对于(7),char型能够表示的数值范围是-128—127,即0X80—0X7F。按照负数补码规则,可知-1的补码为0XFF,-2的补码是0XFE…,当i值为127时,a[127]的值为-128,此时右边整型数0XFFFFFF80转换后,正好是左边char型能够表示的最小负数0X80。当i继续增加,右边为-129,其对应的十六进制补码是0XFFFFFF7F,而char只有8位,故转换时高位被丢弃,左边得到0X7F,正好是char型能够表示的最大正数127。当i继续增加到255时,右边整型数-256的补码是0XFFFFF100(正数256的原码是0X00000100,其反码是0XFFFFF0FF,故-256的补码是0XFFFFF100),低8位为0,a[255]
= 0。然后当i增加到256时,-257(补码是0XFFFFFEFF)的低8位为0XFF,即a[256]
= 0XFF(0XFF是-1的补码),如此又开始一轮的循环。

32位小端机上下面程序的输出结果是什么

int main()
{
long long a = 1, b = 2, c = 3;
printf("%d %d %d\n", a, b, c);  		//1 0 2
return 0;
}

分析:首先,sprintf/fprintf/printf/sscanf/fscanf/scanf等这一类的函数,它们的调用规则(calling conventions)是cdecl,cdecl调用规则的函数,所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。函数参数的传递都是放在栈里面的,而且是从右边的参数开始压栈,printf()是不会对传递的参数进行类型检查的,它只有一个format
specification fields的字符串,而参数是不定长的,所以不能对传递的参数做类型检查,也不能对参数的个数进行检查。在压栈的时候,参数列表里的所有参数都压入栈中了,它不知道有多少个参数。

编译器是怎么去定义压栈的行为的?是先把这long long类型转换为int型再压栈?还是直接压栈?

在32位机器上,64位的整数被拆分为两个32位整数,printf会把64位的按照两个32的参数来处理。此时printf会认为实际的参数为6个,而不是3个。

c、b、a压栈之后,在最低的12字节处是a和b,a占2*4个bytes,b占1*4个byte。b先压入栈,a后压入栈。因为是小端机,即每个数字的高字节在高地址,低字节在低地址。而栈的内存生长方向是从大到小的,也就是栈底是高地址,栈顶是低地址,所以a的低字节在低地址。

那么输出的时候,format specification fields字符串匹配栈里面的内容,首先一个%d取出4个bytes出来输出,然后后面又有一个%d再取出4个bytes出来打印。所以结果就是这样了。也就是说刚开始压入栈的c的值在输出的时候根本都没有用到。

输出:1 0 2

引申:输出格式符说明

%lu 输出无符号10进制长整型数;

%u 输出无符号10进制整型数;

%ld用来输出10进制长整型数;

%d用来输出10进制int整型数;

%f用来输出10进制单精度、双精度浮点数,默认输出6位小数。

格式符指明了输出指定的起始地址开始的若干个字节的内容(把它们作为长整型数或整型数来解释),如果用错了对象,就会得出意想不到的结果。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息