void指针及其应用,C语言void指针及使用注意事项详解
void 指针是一种特殊的指针,表示为“无类型指针”,在 ANSI C 中使用它来代替“char*”作为通用指针的类型。由于 void 指针没有特定的类型,因此它可以指向任何类型的数据。也就是说,任何类型的指针都可以直接赋值给 void 指针,而无需进行其他相关的强制类型转换,如下面的示例代码所示:
虽然如此,但这并不意味着可以无需任何强制类型转换就将 void 指针直接赋给其他类型的指针,因为“空类型”可以包容“有类型”,而“有类型”则不能包容“空类型”。正如我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”一样。因此,下面的示例代码将编译出错,如果在 VC++2010 中,将提示“a value of type"void*"cannot be assigned to an entity of type"int*"”的错误信息。
由此可见,要将 void 指针赋值给其他类型的指针,必须进行强制类型转换。如下面的示例代码所示:
在上面的示例代码中,指针变量 p 的类型是“int*”,指向的类型是 int,被初始化为指向整型变量 a。
在执行语句“p++”时,编译器是这样处理的:把指针 p 的值加上了“sizeof(int)”(由于在 32 位系统中,int 占 4 字节,所以这里是被加上了 4),即 p 所指向的地址由原来的变量 a 的地址向高地址方向增加了 4 字节。但又由于 char 类型的长度是一个字节,所以语句“printf("%s",p)”将输出“tyuiopasdfghjkl”。
而对于 void 指针,编译器并不知道所指对象的大小,所以对 void 指针进行算术操作都是不合法的,如下面的示例代码所示:
上面的代码在 VC++2010 中将提示“expression must be a pointer to a complete object type”的错误信息。
但值得注意的是,GNU 则不这么认为,它指定“void*”的算法操作与“char*”一致。因此下列语句在 GNU 编译器中都是正确的:
下面的示例代码演示了在 GCC 中执行对 void 指针的自增操作:
运行结果为:
LoveC
由此可见,GNU 和 ANSI 还存在着一些区别,相比之下,GNU 较 ANSI 更“开放”,提供了对更多语法的支持。但是在真实的设计环境中,还是应该尽可能符合 ANSI 标准,尽量避免对 void 指针进行算术操作。
比较典型的函数有内存操作函数 memcpy 和 memset,如下面的代码所示:
这样,任何类型的指针都可以传入 memcpy 函数和 memset 函数中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。memcpy 函数的调用示例如下面的代码所示:
或者进行如下形式的调用:
因为参数类型是 void*,所以上面的调用都是正确的。现在假设 memcpy 函数的参数类型不是 void*,而是 char*,如下面的代码所示:
现在继续执行如下形式的调用:
由于类型不匹配,编译器就会报错,如图 1 所示。

图 1
由此可见,这样的函数同时也失去了通用性。
- void *p1;
- int *p2;
- …
- p1 = p2;
- void *p1;
- int *p2;
- …
- p2 = p1;
- void *p1;
- int *p2;
- …
- p2 = (int*)p1;
避免对void指针进行算术操作
ANSI C 标准规定,进行算法操作的指针必须确定知道其指向数据类型大小,也就是说必须知道内存目的地址的确切值。如下面的示例代码所示:
- char a[20]="qwertyuiopasdfghjkl";
- int *p=(int *)a;
- p++;
- printf("%s", p);
在执行语句“p++”时,编译器是这样处理的:把指针 p 的值加上了“sizeof(int)”(由于在 32 位系统中,int 占 4 字节,所以这里是被加上了 4),即 p 所指向的地址由原来的变量 a 的地址向高地址方向增加了 4 字节。但又由于 char 类型的长度是一个字节,所以语句“printf("%s",p)”将输出“tyuiopasdfghjkl”。
而对于 void 指针,编译器并不知道所指对象的大小,所以对 void 指针进行算术操作都是不合法的,如下面的示例代码所示:
- void * p;
- p++; // ANSI:错误
- p+= 1; // ANSI:错误
但值得注意的是,GNU 则不这么认为,它指定“void*”的算法操作与“char*”一致。因此下列语句在 GNU 编译器中都是正确的:
- void * p;
- p++; // GUN:正确
- p+=1; // GUN:正确
- #include <stdio.h>
- int main(void)
- {
- void * p="ILoveC";
- p++;
- printf("%s\n", p);
- }
LoveC
由此可见,GNU 和 ANSI 还存在着一些区别,相比之下,GNU 较 ANSI 更“开放”,提供了对更多语法的支持。但是在真实的设计环境中,还是应该尽可能符合 ANSI 标准,尽量避免对 void 指针进行算术操作。
如果函数的参数可以是任意类型指针,应该将其参数声明为 void*
前面提到,void 指针可以指向任意类型的数据,同时任何类型的指针都可以直接赋值给 void 指针,而无需进行其他相关的强制类型转换。因此,在编程中,如果函数的参数可以是任意类型指针,那么应该使用 void 指针作为函数的形参,这样函数就可以接受任意数据类型的指针作为参数。比较典型的函数有内存操作函数 memcpy 和 memset,如下面的代码所示:
- void *memset(void *buffer, int b, size_t size)
- {
- assert(buffer!=NULL);
- char* retAddr = (char*)buffer;
- while (size--> 0)
- {
- *(retAddr++) = (char)b;
- }
- return retAddr;
- }
- void *memcpy (void *dst, const void *src, size_t size)
- {
- assert((dst!=NULL) && (src!=NULL));
- char *temp_dest = (char *)dst;
- char *temp_src = (char *)src;
- char* retAddr = temp_dest;
- size_t i = 0;
- /* 解决数据区重叠问题*/
- if ((retAddr>temp_src) && (retAddr<(temp_src+size)))
- {
- for (i=size-1; i>=0; i--)
- {
- *(temp_dest++) = *(temp_src++);
- }
- }
- else
- {
- for (i=0; i<size; i++)
- {
- *(temp_dest++) = *(temp_src++);
- }
- }
- *(retAddr+size)='\0';
- return retAddr;
- }
- char buf[]="abcdefg";
- // buf+2(从c开始,长度3个,即cde)
- memcpy(buf, buf+2 ,3);
- printf("%s\n", buf);
- int dst[100];
- int src[100];
- memcpy(dst, src, 100*sizeof(int));
- char *memcpy(char* dst, const char* src, size_t size)
- {
- assert((dst !=NULL) && (src != NULL));
- char *retAddr = dst;
- size_t i = 0;
- if ((retAddr>src) && (retAddr<(src+size)))
- {
- for (i=size-1; i>=0; i--)
- {
- *(dst++)= *(src++);
- }
- }
- else
- {
- for (i=0; i<size; i++)
- {
- *(dst++) = *(src++);
- }
- }
- *(retAddr+size)='\0';
- return retAddr;
- }
- int dst[100];
- int src[100];
- memcpy(dst, src, 100*sizeof(int));

图 1
由此可见,这样的函数同时也失去了通用性。
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算