4.6.5 指针—整数转换
再回到指针的话题。很多初学者喜欢把指针变量的值看成整数,但这有点像把面粉和馒头相提并论,毕竟它们至少在类型上并不相同。在下面的代码片段中,变量p的类型是指向int的指针,但2000的类型是int,不能用于初始化一个指针类型的变量,所以这个声明是非法的;在第二行,一元*运算符要求它的操作数是指针类型,但2008的类型是int,所以非法。我建议你创建一个带有main函数的源文件,将这两行添加到main函数里,然后尝试是否能通过翻译,并观察诊断信息都说了些什么。
int * p = 2000; * 2008 = 10086;
然而,对于C这样宽容的语言来说,如果你非要将整数转换为指针,也不是不可以,因为我们有转型运算符可以使用:
int * p =(int *)2000; *(int *)2008 = 10086;
在第一行里,转型表达式(int *)2000将int类型的2000转换为指向int的指针类型,然后用于初始化变量p;在第二行里,转型表达式(int *)2008将int类型的2008转换为指向int的指针,然后,一元*运算符作用于这个指针(类型的值),得到一个左值,然后将10086赋给左值所代表的变量。
必须要郑重指出的是,上面的做法虽然合法,但运行的时候很危险,程序崩溃的概率几乎是百分之百。原因是,2000和2008通常不会是一个合法有效的变量地址,你不知道这个地址是用来干什么的,如果它能够执行,那你就是破坏了人家原有的代码或者数据;如果它不能正常执行,那就意味着它是一片受处理器和操作系统保护的内存区域,也可能并不对应着任何实际存在的物理内存。
相比之下,下面这个程序可能会稍好一些,但也不能保证会在所有计算机上得到正确的结果,这是因为,在不同的计算机系统上,指针类型的长度可能并不相同。
/***********c0409.c**********/ int main(void) { int m = 0; unsigned long long int ull; ull =(unsigned long long)& m; *(int *)ull = 10086; }
以上,我们先是声明了一个int类型的变量m和一个unsigned long long类型的变量ull;然后,子表达式& m的类型是指向int的指针,这也是其值的类型。这个指针类型的值被转换为unsigned long long类型后赋给变量ull。因为不知道指针的长度,我们使用一个最长的整数类型unsigned long long来保存转换后的结果。
接着,在表达式*(int *)ull = 10086里,左值ull执行左值转换,转换为一个unsigned long long类型的值。这个值被转型运算符转换为指向int的指针,然后一元*运算符作用于这个指针,得到一个int类型的左值,这个左值接受赋值。至此,因为表达式(int *)ull的值实际上指向变量m,故变量m的存储值是10086。
练习4.10
1.表达式(unsigned long long)& m和(unsigned long long)m所执行的转换过程有什么区别?
2.编写一个程序完成以下工作:将一个指向函数的指针转换为整数,再将这个整数转换为指向函数的指针,最后,用这个指针调用那个函数。
4.6.5.2 空指针
除非使用转型表达式,用一个整数来初始化指针类型的变量或者给指针类型的变量赋值通常是不可行的,但有一个例外,那就是整型常量0,它不需要任何显式的转换:
signed char * ps1 = 0, * ps2; ps2 = 0;
在C语言里,值为0的整型常量表达式可以自动转换为任何类型的指针,转换后的结果称为那种类型的空指针。空指针意味着它不指向任何有效的变量或者函数。