前言
这篇将介绍C语言令人头疼的知识点–指针。介绍void *这个特别的指针,介绍指针内存分配、指针作为函数传参改怎么使用以及为什么不能直接对void *指针做一些操作。
一、void *指针是什么?
C语言中,一共有几种指针类型,其中最特别的就是void *指针。void *指针就是指向任何类型的指针,在编译的时候不会确定其指向的类型,是在程序中进行指向的。
void*指针简单使用
上面说了,void *指针是可以指向任何类型的指针,那让我们看看具体改怎么使用叭。
int main()
{
int i_t[2]={0,1};
void *v_t=i_t;
int * p_t=(int *)v_t;
printf("Get int * i_t[0] = %d i_t[1] = %d \n",*p_t,*(p_t+1));
v_t=p_t;
printf("Get void * i_t[0] = %d i_t[1] = %d \n",*(int *)v_t,* (int *)(v_t+4));
system("pause");
return 0;
}
运行结果
Get int * i_t[0] = 0 i_t[1] = 1
Get void * i_t[0] = 0 i_t[1] = 1
注意,void *在使用的时候,对void *进行地址的+或者-操作,一定要进行操作需要进行强制类型转换来进行操作,不是所有编译器都支持GNU标准的。
二、指针内存大小
指针所占大小
指针大小其实是由编译器和操作系统共同决定的,一般规律如下。
- 32位操作系统,32位编译器,指针大小是32位
- 64位操作系统,32位编译器,指针大小是32位
- 64位操作系统,64位编译器,指针大小是64位
32位指针大小,能够寻址的空间是4G,62位指针大小能够寻址1800T的地址,这个仅作为了解即可。
void *类型指针与其他类型指针区别
基本区别
区别可以看下图,指针所在的内存都是一样的,但是void *指针与其他指针不一样的就是,它包含所指向类型的信息。这个特性就意味着,它可以指向任何类型的变量,void *类型指针只是指向了这个变量的地址而已。
取值操作与地址增长操作
在ANSI的标准下,在void *类型如果不强制类型转换是不能够进行取值与地址的增长的操作。
为什么呢?我们先看一下int *类型的指针是怎么进行取值的操作的。
取值操作
int *是取得值的范围是(当前首地址+int类型偏移大小)。double *则是当前首地址+double类型偏移大小。这里的偏移大小就是所占的字节数,也就是sizeof(int)或者sizeof(double)。
void *指针是没有包含其指向类型的信息,也就是在不使用强制类型转换下,编译器不知道void *的取多少偏移地址的范围才是所指向的值所在地址范围。也就是void *仅仅具有当前指向目标的首地址,并不知道取首地址之后多少字节合适,这样来取直,究竟是4字节还是8字节,编译器它不知道。它不够智能,得需要告诉这个蠢货,使用强制类型告诉它。
地址增长
地址增长和取值是类似的,void *不知道++改移动多少字节合适,就不具体论述了,聪明的你心中一定有答案了。
三、void *指针作为函数传参
这里我们将使用void *指针作为传参,使得程序的泛用性更强。这里我们就实现两个个C库中的memset和memcpy,边实现边回顾上面的知识点,就知道为什么传入参数是void *类型以及为什么需要指定长度。
实现memset
函数原型:返回首地址,传入需要指定需要指向初始化的地址以及想要初始化的值和长度。
void * memset(void *s,int c,size_t n)
函数功能:初始化函数,将某一块内存中的全部设置为指定的值。
请看下面的代码和测试函数:
void * my_memset(void *s,int c,size_t n)
{
char *c_s=(char *)s;
while(n--) *c_s++=c;
return s;
}
int main()
{
int i_t[2]={1,1};
my_memset(i_t,0,sizeof(i_t));
for(int i=0;i<(sizeof(i_t)/sizeof(int));i++)
printf(" %d ",i_t[i]);
system("pause");
return 0;
}
运行结果:
0 0
实现memset
函数原型:
void *memcpy(void *dest, void *src, unsigned int count);
函数功能:由src所指内存区域复制count个字节到dest所指内存区域
代码以及测试函数:
void * my_memcpy(void *dest,void *src,unsigned int size)
{
char *b_dest=(char *)dest,*b_src=(char *)src;
unsigned int len;
for(len=size;len>0;len--)
{
*b_dest++=*b_src++;
}
return dest;
}
int main()
{
int i_t[2]={1,1};
int i_f[2]={0,0};
my_memcpy(i_f,i_t,sizeof(i_t));
for(int i=0;i<(sizeof(i_t)/sizeof(int));i++)
printf(" %d ",i_f[i]);
system("pause");
return 0;
}
代码思路简单分析: 复制和清零的思路都是对传入的地址,进行一位一位的操作。
上面说了,void *不包含类型,所以可以作为任意类型进行传入,这样就达到可以操作任意类型。传入的仅仅是没有类型的地址信息而已。在C中由于指针本身并不包含需要操作长度的信息,因此必须传入需要操作的长度信息。不然仅仅传入指针编译器并不会知道究竟操作的范围。
总结
使用void *指针能够加强函数的适用性,但是操作void *指针需要非常的注意,不然容易导致指针操作越界,导致一些其他不可知的程序错误。
|