指针的实质以及指针的强制转换

   1   什么是指针(指针变量 简称指针),这个问题肯定困惑了很多人,其实指针真的很简单,其实指针变量就只是一个变量,它和其他的变量 ,比如我们常见的int,float等类型的变量有区别吗,我认为是没有的(除了一点)。首先他们都是变量,都是用来存储"数据"的,只是int,float等变量通常存储的是数据,而指针存储的是地址,地址说穿了也是数据,只是我们很少直接使用它,更多的时候是通过它来找到该地址里所存储的数据。所以指针变量(指针)和一般的数据类型是没有区别的。

  2 指针的大小:我们以32位的操作系统作为环境,我们直到,int是4个字节,double占8个字节,而char只有一个字节。 但是我们经常听到一句话,指针是4个字节的,不管它是int类型还是double类型,或者是某个很大结构体的指针,这是为什么呢? 

 我们看看下面这段代码

  

#include<stdio.h>
struct pointer
{
        long address;
        int a;
};
int main()
{
        int *p1;
        double *p2;
        struct pointer *p3;
        printf("p1的大小为%d个字节\n",sizeof(p1));
        printf("p2的大小为%d个字节\n",sizeof(p2));
        printf("p3的大小为%d个字节\n",sizeof(p3));
        return 0;
}
那么运行的结果是什么呢?


为什么是这个结果呢?我们来看看p1,p2,p3到底是什么?

#include<stdio.h>
struct pointer
{
        long address;
        int a;
};
int main()
{
        int num1=1;
        double num2=1.0;
        struct pointer num3;
        int *p1=&num1;;
        double *p2=&num2;;
        struct pointer *p3=&num3;
        printf("p1的值为%p\n",p1);
        printf("p1的值为%p\n",p2);
        printf("p1的值为%p\n",p3);
        return 0;
}

其输出结果为:


我们看到,其实p1,p2,p3都是一个很大的数,我们以%p的格式打印出来,也就是以16进制的方式打印。我们知道,指针代表的就是地址,所以在32位计算机里,他的确只有4个字节,因为32位计算机里的地址是32位寻址,所以打印出来的是4个字节。所以只要他是个指针,在32位计算机里,就是4字节。

  我们再来看看指针在汇编下的表示形式:

#include<stdio.h>
int gi;
int *pi;
int main()
{
  pi=&gi;
  *pi=12;
}

对应的汇编:

  pi=&gi;
0041138E C7 05 40 71 41 00 44 71 41 00 mov      dword ptr [pi (417140h)],offset gi (417144h) 
  *pi=12;
00411398 A1 40 71 41 00                mov         eax,dword ptr [pi (417140h)] 
0041139D C7 00 0C 00 00 00             mov         dword ptr [eax],0Ch 

我们看到,在汇编下,指针的确就是4个字节,我们通过一条mov指令,将变量gi的地址,赋值给力变量pi,然后在以pi作为地址,把常数12放入其中。也就是是说,指针在汇编下,始终表现为地址,似乎我们看不到指针的类型是怎么体现的??怎么办,呵呵,别急

3指针的类型

        似乎汇编下面我们看不到指针的类型,那么在c语言里,怎么还有指针类型的不同,比如folat*,int*等,有意思吗?

  当然是有的,呵呵。我们想一想,我们怎么使用指针的,最常见的就是解引用符号*,那么当我们对一个指针进行解引用时,CPU应该读取以指针为地址开头的几个字节的数据呢?是的,这个时候我们应该传达给CPU一个信息,告诉它应该读取几个字节,这个信息就是指针的类型。

我们仔细观察上面的汇编代码,我们发现有一个这样的代码: dword ptr.  ptr的意思是指明到底是按照什么样的长度去操作机器指令,dword ptr表示安装双字节操作寄存器或者相关内存。类似,word ptr表示安装字的长度来操作。那么,这个ptr前面的dwod,word会不会和指针的类型有关系,我们看看下面这段代码:

#include<stdio.h>
int gi;
int *pi;
short a;
short* pa;
char c;
char *pc;
int main()
{
  pi=&gi;
  *pi=12;
  pa=&a;
  *pa=10;
  pc=&c;
  *pc='a';
}

其对应的汇编代码:

  pi=&gi;
004117EE C7 05 D4 74 41 00 E0 74 41 00 mov         dword ptr [pi (4174D4h)],offset gi (4174E0h) 
  *pi=12;
004117F8 A1 D4 74 41 00   mov         eax,dword ptr [pi (4174D4h)] 
004117FD C7 00 0C 00 00 00 mov         dword ptr [eax],0Ch 
  pa=&a;
00411803 C7 05 CC 74 41 00 DC 74 41 00 mov         dword ptr [pa (4174CCh)],offset a (4174DCh) 
  *pa=10;
0041180D B8 0A 00 00 00   mov         eax,0Ah 
00411812 8B 0D CC 74 41 00 mov         ecx,dword ptr [pa (4174CCh)] 
00411818 66 89 01         mov         word ptr [ecx],ax 
  pc=&c;
0041181B C7 05 D0 74 41 00 D8 74 41 00 mov         dword ptr [pc (4174D0h)],offset c (4174D8h) 
  *pc='a';
00411825 A1 D0 74 41 00   mov         eax,dword ptr [pc (4174D0h)] 
0041182A C6 00 61         mov         byte ptr [eax],61h 

我们发现,当我们对指针做解引用运算时,对应的汇编中ptr前面的标识数据长度的标识都变了.,int 对应dword 即四字节

short int 对应的是 word 即两个字节  char 对应的是byte  恰好是一个字节。

哈哈,发现了吧,这就是指针类型的体现,它向CPU指示了应该按照几个字节来操作内存数据。

同时,我们也就理解了指针的加法,为什么int* +1是4个字节,而short *+1 是两个字节,char*+1只有一个字节。  由于void*没有确切的数据类型,所以它无法做加法运算,因为它不知道到底操作了几个字节。

  所以,c语言的指针类型包含:地址以及类型信息。

4指针的强制转换

 我们来思考这样一个问题,既然指针的实质就是地址,那么我们能否将不同类型的指针相互赋值呢?

我们看看下面的代码

#include<stdio.h>
int gi;
int *pi;
short a;
short* pa;
int main()
{
 pi=pa;
}
这段代码能顺利运行吗?呵呵,我们看看编译器怎么说?
 error C2440: “=”: 无法从“short *”转换为“int *”
为什么不行呢,其实仔细想想,pi是一个整形指针,每次对它做解引用操作时会读取4个字节的内存数据,而pa是一个short类型的指针,本身只对应2个字节的数据,假设能成功进行pi=pa的赋值操作,将来操作*pa就会读取4个字节的数据,必然会引起越界。而编译器为了帮助我们避免越界的错误,就在编译时报错。

那么,强制转换怎么样呢?我们来讨论一下指针的强制转换,看看下面的一段代码:

#include<stdio.h>
int i;
int *pi;
short*ps;
char*pc;
int main()
{
 pi=&i;
 ps=(short*)&i;
 pc=(char*)&i;

}
其对应的汇编:
 pi=&i;
004117EE C7 05 D8 74 41 00 D4 74 41 00 mov         dword ptr [c (4174D8h)],offset i (4174D4h) 
 ps=(short*)&i;
004117F8 C7 05 CC 74 41 00 D4 74 41 00 mov         dword ptr [ps (4174CCh)],offset i (4174D4h) 
 pc=(char*)&i;
00411802 C7 05 D0 74 41 00 D4 74 41 00 mov         dword ptr [pc (4174D0h)],offset i (4174D4h) 

我们看到,这3条强制转换指令没有产生任何与类型相关的指令。可知,强制转换就是给编译器提个醒,不会有对应的代码。

我们看看对转换后的指针做解引用

#include<stdio.h>
int i;
int *pi;
short*ps;
char*pc;
int main()
{
 pi=&i;
 ps=(short*)&i;
 pc=(char*)&i;
 *pi=0x1234;
 *ps=0x1234;
 *pc=0x12;
}

解引用对应的汇编代码:

 *pi=0x1234;
0041180C A1 D8 74 41 00   mov         eax,dword ptr [c (4174D8h)] 
00411811 C7 00 34 12 00 00 mov         dword ptr [eax],1234h 
 *ps=0x1234;
00411817 B8 34 12 00 00   mov         eax,1234h 
0041181C 8B 0D CC 74 41 00 mov         ecx,dword ptr [ps (4174CCh)] 
00411822 66 89 01         mov         word ptr [ecx],ax 
 *pc=0x12;
00411825 A1 D0 74 41 00   mov         eax,dword ptr [pc (4174D0h)] 
0041182A C6 00 12         mov         byte ptr [eax],12h 

我们观察赋值语句的ptr前面的符号,发现和指针类型是对应的。

那么,既然强制转换不会出错,那么是不是我们可以经常使用指针的强制转换?

#include<stdio.h>
int i;
int *pi;
short*ps;
char*pc;
int main()
{
 ps=(shotr*)pi;
}
上面这段代码,有没有问题?

呵呵,没有的,为什么?因为pi对应4个字节的数据,把它的地址复制给ps,即使我们做解引用操作时,只会读取pi对应的4个字节的前两个字节。

#include<stdio.h>
int i=0x12345678;
int *pi=&i;
short*ps;
char*pc;
int main()
{
 ps=(short*)pi;
 printf("%x\n",*ps);
}
输出的结果为:



为什么是5678呢,呵呵当然了,小段模式嘛。

我们把上面的代码变一下

#include<stdio.h>
int i;
int *pi;
short* ps;
char* pc;
int main()
{
 pi=(int*)ps;
}
这样会有问题吗?

呵呵,思考一下,发现,有。ps对应的数据只有2个字节,我们把它的地址复制给了pi,再对pi做解引用操作时,会读取以该地址起始的4个字节的数据,前两个字节是ps对应的数据,但是我们不知道它后面的数据是否能访问,所以很可能会报错,即使不报错,由于后面两个字节的不可预测(目前不知道是啥),所以得到的*pi的值是垃圾数据。

 其实,也就是说,可以把大类型的数据截断,这个不会出错。但是对小类型数据进行扩展,很可能出错(大类型与小类型是指数据对应的类型,即占几个字节)

呵呵,指针就到这里吧。

PS:

顺便说一下,最前面说过指针和普通类型变量几乎是一样的,所以对于普通类型变量二等强制转换,其实质都是一样的,数据始终在内存里面存着,而所谓的int,short类型只是告诉编译器读取几个字节的数据,至于会不会出错,就看是否会出现数据的不可预测性(垃圾数据或报错)。再说一句,真正看对应结果时应该去看看数据在内存中的存储方式,主要是主要浮点数的存储和整数不同,所以浮点数的转换有时会出现看起来很莫名其妙的值。

呵呵,就是这样~

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值