从变量的类型转换看C语言的思维模式

怪异的语法

C语言的类型转换语法对大多数人是非常奇怪的,尤其是对于接触的第一门语言不是C语言的人来说。在大多数“正常”的语言中,类型转换的语法应该是这样的(这里以Python为例):

a = 1
b = float(a)

但C语言中的类型转换是这个样子的:

unsigned int b = 0x1812;
unsigned char a = (unsigned char)b;

不同的语言模式

造成这种差异的原因并不是因为语法设计的不同。而是语言设计的思路不一样。Python是面向对象的语言,int和float都是内置的类,表达式 float(a) 实际上是将int类型的a作为参数,生成一个float类的实例,这种类型转换实际上是生成了一个新的对象。

而在C语言中,是没有对象的,而且不同的类型之间也似乎没有什么明确的界限,一个变量不过是一个字节序列。C语言中,有符号整型int和无符号整型unsigned都是4个字节大小。字节序列0xffffffff,如果看成int类型的话,是-1,而看成unsigned类型则是4294967295。C语言中的printf函数可以明显地看出这一特点,printf函数并不检查参数的类型,而是根据描述符说明的类型去解释变量,如果描述的类型和实际的类型不匹配,很可能出现一些奇怪的现象。

例如如下代码

#include <stdio.h>

int main()
{
	int d = -1;
	printf("%d\n", d);
	printf("%u\n", d);
	return 0;
}

输出结果为:

-1
4294967295

变量d本来是int类型的,字节序列为0xffffffff,用%u描述符输出时,C编译器直接将0xffffffff作为无符号整型解释,得到4294967295. 因此我们可以理解到以下的原理:

C语言类型转换的原理

C语言的强制类型转换中,变量的字节序列不变,只是字节的解释方式发生了改变。

为了进一步体会这种特性,我们将程序编译后,观察编译器生成的汇编代码,进一步进行验证。

#include <stdio.h>

int main()
{
	int a = -4;
	unsigned b = (unsigned)a;
	unsigned char c = (unsigned char)b;
	printf("%d %u\n", a, b);
	printf("%x %x\n", a, c);
	return 0;
}

输出结果如下:

-4 4294967292
fffffffc fc

对.o文件的反汇编结果如下:

00000000 <main>:
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	83 e4 f0             	and    $0xfffffff0,%esp
   6:	83 ec 20             	sub    $0x20,%esp
   9:	c7 44 24 1c fc ff ff 	movl   $0xfffffffc,0x1c(%esp)
  10:	ff 
  11:	8b 44 24 1c          	mov    0x1c(%esp),%eax
  15:	89 44 24 18          	mov    %eax,0x18(%esp)
  19:	8b 44 24 18          	mov    0x18(%esp),%eax
  1d:	88 44 24 17          	mov    %al,0x17(%esp)
  21:	8b 44 24 18          	mov    0x18(%esp),%eax
  25:	89 44 24 08          	mov    %eax,0x8(%esp)
  29:	8b 44 24 1c          	mov    0x1c(%esp),%eax
  2d:	89 44 24 04          	mov    %eax,0x4(%esp)
  31:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
  38:	e8 fc ff ff ff       	call   39 <main+0x39>
  3d:	0f b6 44 24 17       	movzbl 0x17(%esp),%eax
  42:	89 44 24 08          	mov    %eax,0x8(%esp)
  46:	8b 44 24 1c          	mov    0x1c(%esp),%eax
  4a:	89 44 24 04          	mov    %eax,0x4(%esp)
  4e:	c7 04 24 07 00 00 00 	movl   $0x7,(%esp)
  55:	e8 fc ff ff ff       	call   56 <main+0x56>
  5a:	b8 00 00 00 00       	mov    $0x0,%eax
  5f:	c9                   	leave  
  60:	c3                   	ret    

从汇编代码中可以看出,将unsigned类型的变量赋值给unsigned char类型,不过是将unsigned类型变量的最低字节取出;将int类型转换为unsigned类型,不过是原封不动的mov.

总结

C语言类型转换的语法设计成在变量前加带括号的类型名,这样的语法正体现了类型转换在C语言中实际发生的情况:用新的类型来“修饰”原先的变量,将变量按照新的类型来解释,并没有产生一个新的变量。

附:所用软件版本

gcc: gcc (Debian 4.7.2-5) 4.7.2
objdump: GNU objdump (GNU Binutils for Debian) 2.22

posted @ 2014-11-14 22:57  nettee  阅读(227)  评论(0编辑  收藏  举报