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

各种类型的表示范围

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

类型名

字节数

其他类型名

10进制表示范围(机器码)

char

1

signed char

-1281270X80-0X7F

unsigned char

1

none

0-2550X00-0XFF

short

2

short int

signed short int

-3276832767

0X8000-0X7FFF

unsigned short

2

unsigned short

0-65535

0X0000-0XFFFF

int

4

signed int

-21474836482147483647

0X80000000-0X7FFFFFFF

unsigend int

4

none

04294967295

0X80000000-0XFFFFFFFF

long

4

long int

signed long int

-21474836482147483647

0X80000000-0X7FFFFFFF

unsigned long

4

unsigned long int

04294967295

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个字节);

2enum会根据最大值来决定类型,一般来说为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型小的数据类型(包括charsigned charunsigned charshortsigned shortunsigned short)转换为int型。如果转换后的数据会超出int型所能表示的范围的话,则转换为unsigned int

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

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

4unsigned intlong类型的转换规律同3,在32位机上,unsigned int32位,范围0 ——4294967295long也是32位,范围–2147483648 ——2147483647,可见long类型不够表示所有的unsigned int型,因此在混有unsigned intlong的表达式中,两者都被转换为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。截取后面8FF赋值给变量a(unsigned char),此时a = 0XFF(a没有符号位,0XFF转换为十进制为255),又将0XFF,直接赋值给char b,此时b = 0XFF(但是要注意,b是有符号的,0XFF是一个负数的补码表示,转换为十进制为整数-1)

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

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

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

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

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

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

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

对于(7),char型能够表示的数值范围是-128127,即0X800X7F。按照负数补码规则,可知-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位为0a[255] = 0。然后当i增加到256时,-257(补码是0XFFFFFEFF)的低8位为0XFF,即a[256] = 0XFF0XFF-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)cdeclcdecl调用规则的函数,所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。函数参数的传递都是放在栈里面的,而且是从右边的参数开始压栈printf()不会对传递的参数进行类型检查的,它只有一个format specification fields的字符串,而参数是不定长的,所以不能对传递的参数做类型检查,也不能对参数的个数进行检查。在压栈的时候,参数列表里的所有参数都压入栈中了,它不知道有多少个参数。

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

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

cba压栈之后,在最低的12字节处是aba2*4bytesb1*4byteb先压入栈,a后压入栈。因为是小端机,即每个数字的高字节在高地址,低字节在低地址。而栈的内存生长方向是从大到小的,也就是栈底是高地址,栈顶是低地址,所以a的低字节在低地址。

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

输出:1 0 2

引申:输出格式符说明

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

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

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

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

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

格式符指明了输出指定的起始地址开始的若干个字节的内容(把它们作为长整型数或整型数来解释),如果用错了对象,就会得出意想不到的结果。





  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值