C语言--指针之空指针(void *)


前言

这篇将介绍C语言令人头疼的知识点–指针。介绍void *这个特别的指针,介绍指针内存分配、指针作为函数传参改怎么使用以及为什么不能直接对void *指针做一些操作。


一、void *指针是什么?

C语言中,一共有几种指针类型,其中最特别的就是void *指针。void *指针就是指向任何类型的指针,在编译的时候不会确定其指向的类型,是在程序中进行指向的。

void*指针简单使用

上面说了,void *指针是可以指向任何类型的指针,那让我们看看具体改怎么使用叭。

int main() 
{ 
  
  int i_t[2]={0,1};
  
  void *v_t=i_t;/*void *可以获得指向任何地址,但不知道指向地址的类型大小*/
  /*普通指针使用,获得void *指针指向的地址并强制转换为int *类型,使之有知道指向类型的大小*/
  int * p_t=(int *)v_t;
  printf("Get int * i_t[0] = %d i_t[1] = %d \n",*p_t,*(p_t+1));

  /*void指针使用,可以直接给void *指针赋值,让它指向的类型改为int*的地址*/  
  v_t=p_t;

  printf("Get void * i_t[0] = %d i_t[1] = %d \n",*(int *)v_t,* (int *)(v_t+4));
  /*printf("Get void * i_t[0] = %d i_t[1] = %d \n",*(int *)v_t,* (char *)(v_t+4));*/ 
  //这两条printf输出效果一样,

  /*第二条printf输出警告,对void * 进行 +操作的警告,在GNU标准,对void *取加操作是仅仅类似char *的取加操作*/
  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库中的memsetmemcpy,边实现边回顾上面的知识点,就知道为什么传入参数是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 

实现memcpy

函数原型:

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 *指针需要非常的注意,不然容易导致指针操作越界,导致一些其他不可知的程序错误。

  • 15
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值