C语言void指针及使用注意事项详解

2023-11-10

void 指针是一种特殊的指针,表示为“无类型指针”,在 ANSI C 中使用它来代替“char*”作为通用指针的类型。由于 void 指针没有特定的类型,因此它可以指向任何类型的数据。也就是说,任何类型的指针都可以直接赋值给 void 指针,而无需进行其他相关的强制类型转换,如下面的示例代码所示:

void *p1;
int *p2;
…
p1 = p2;

虽然如此,但这并不意味着可以无需任何强制类型转换就将 void 指针直接赋给其他类型的指针,因为“空类型”可以包容“有类型”,而“有类型”则不能包容“空类型”。正如我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”一样。因此,下面的示例代码将编译出错,如果在 VC++2010 中,将提示“a

value of type"void*"cannot be assigned to an entity of type"int*"”的错误信息。
void *p1;
int *p2;
…
p2 = p1;

由此可见,要将 void 指针赋值给其他类型的指针,必须进行强制类型转换。如下面的示例代码所示:

void *p1;
int *p2;
…
p2 = (int*)p1;

避免对void指针进行算术操作

ANSI C 标准规定,进行算法操作的指针必须确定知道其指向数据类型大小,也就是说必须知道内存目的地址的确切值。如下面的示例代码所示:

char a[20]="qwertyuiopasdfghjkl";
int *p=(int *)a;
p++;
printf("%s", p);

在上面的示例代码中,指针变量 p 的类型是“int*”,指向的类型是 int,被初始化为指向整型变量 a。

在执行语句“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:错误

上面的代码在 VC++2010 中将提示“expression must be a pointer to a complete object type”的错误信息。

但值得注意的是,GNU 则不这么认为,它指定“void*”的算法操作与“char*”一致。因此下列语句在 GNU 编译器中都是正确的:

void * p;
p++;      // GUN:正确
p+=1;      // GUN:正确
下面的示例代码演示了在 GCC 中执行对 void 指针的自增操作:
#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;
}

这样,任何类型的指针都可以传入 memcpy 函数和 memset 函数中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。memcpy 函数的调用示例如下面的代码所示:

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));

因为参数类型是 void*,所以上面的调用都是正确的。现在假设 memcpy 函数的参数类型不是 void*,而是 char*,如下面的代码所示:

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 所示。
在这里插入图片描述
由此可见,这样的函数同时也失去了通用性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言void指针及使用注意事项详解 的相关文章

随机推荐

  • cmd停止情况

    情况描述 win10在使用cmd时 鼠标点击后出现cmd整个停止的情况 例如 在下载时 鼠标左键点击了cmd黑框里的内容 结果下载停止了 解决方式 出现这一情况的原因是 cmd开启了快速编辑模式 在cmd上框右键属性 关闭即可
  • 面向对象编程的六大原则

    一 面向对象编程的六大原则 单一责任原则 对类来说的 即一个类应该只负责一项职责 如类A负责两个不同职责 职责1 职责2 当职责1需求变更而改变A时 可能造成职责2执行错误 所以需要将类A的粒度分解为A1 A2 接口隔离原则 客户端不应该依
  • Pycharm Debug(断点调试)超详细攻略

    前言 PyCharm Debug 可以帮助开发者在代码运行时进行实时的调试和错误排查 提高代码开发效率和代码质量 当然也可以对源码进行断点调试 领略源码的魅力 具体操作步骤 准备一段代码 让我们来举个简单的栗子 这段代码主要作用 循环ran
  • VUE基本指令(v-model,v-html,v-text,v-bind,v-if,v-show,v-for,v-on:click,组件,过滤器)

    文章目录 双向数据绑定 v bind v bind title简化写法为 title 设置类名 v bind class 隐藏 显示元素 v if和v show v for 遍历数组 遍历对象 v on click 点击事件 简化语法 cl
  • 未授权访问漏洞1

    未授权访问漏洞产生的原因 未授权访问漏洞是站由于网站管理员对站点的资源所拥有的权限或站点配置文件没有进行合理的配置 导致没有进行授权的用户可以访问到高级资源 常见的未授权访问漏洞 1 Redis未授权访问 漏洞简述 Redis是一种缓存数据
  • 实现统计某个目录中的java文件个数(子目录也算进去)

    实现统计某个目录中的java文件个数 子目录也算进去 package com summer io01 import java io File public class Demo07 public static int javaFileNum
  • python输出特征相关矩阵_两个特征矩阵的有效成对相关

    似乎 遵循了皮尔逊相关系数公式的定义 该公式适用于A amp B 基于这个公式 你可以很容易地将向量化 因为A和 列的成对计算是相互独立的 这里有一个使用 Get number of rows in either A or B N B sh
  • STM32 IIC通信-硬件从机 cube-HAL库

    前言 搞过很长时间的stm32 了 但是一直没有深入的研究底层 iic方面之前多是作为主机 而且多是使用io口模拟的 网上在这方面有用的东西确实不多 由于工作需要学习了下iic硬件从机的使用 使用cube创建工程 hal库 上次用cube还
  • sublime text3中代码格式化

    有两种方式 1 选中要格式化的代码 然后依次选择以下菜单 Edit gt Line gt Reindent 2 依次选择以下菜单 Preference gt Key Bindings user 然后 自己设置快捷键 keys ctrl sh
  • 告白玫瑰

    关注微信公众号 ClassmateJie 更多惊喜等待你的发掘 直接看实现效果 电脑端 手机端 使用场景 发给女神告白 提供一些文案 自从遇见你 我的世界变得不一样了 每一天都因为你而变得特别 我想告诉你 我喜欢你 不仅仅是因为你的美丽 还
  • 【win10】电脑剪贴板失效,解决办法。

    1 打开任务管理器 把剪贴板的进程结束 2 打开运行 输入rdpclip exe 即可解决
  • 状态压缩DP

    状态压缩DP前置知识 问题简介 基于状态压缩的动态规划 又叫集合动态规划 顾名思义 这是一类以集合信息为状态的特殊的动态规划问题 主要有传统集合动态规划和基于连通性状态压缩的动态规划两种 一般的动态规划往往着眼于整体 从中提取出两三个关键信
  • docker 安装mongodb

    1 取最新版的 MongoDB 镜像 gt docker pull mongo latest 2 查看本地镜像 gt docker images REPOSITORY TAG IMAGE ID CREATED SIZE mongo late
  • 数据库查询优化

    文章目录 1 代码优化 2 定位到慢SQL上 并优化 3 合理使用索引 重点 4 分表查询 5 缓存 6 异步 多线程 1 代码优化 减少没有必要的代码 例如for循环次数过多 作了很多无谓的条件判断 相同逻辑重复多次等 2 定位到慢SQL
  • 微服务分布式构架开发实战 附下载地址

    微服务是一种软件架构风格 目标是将一个复杂的应用拆分成多个服务模块 每个模块专注单一业务功能对外提供服务 并可以独立编译及部署 同时各模块间互相通信彼此协作 组合为整体对外提供完整服务 以往的图书大多只针对微服务分布式架构自身的知识点讲解
  • Linux iptables常用命令

    iptables 是 Linux 中重要的访问控制手段 是俗称的 Linux 防火墙系统的重要组成部分 这里记录了iptables 防火墙规则的一些常用的操作指令 下面的操作以 CentOS 为基础介绍 应该对不同的 Linux 发行版都差
  • 日志分析系列之平台实现

    本系列故事纯属虚构 如有雷同实属巧合 平台实现前的说明 小B在给老板汇报了 统一日志分析平台 项目后 老板拍板立即开始做 争取下一次能及时发现攻击并且追踪攻击者 于是小B开始分析了市面上商业与开源的日志分析平台架构 大家都神似如下图 知道了
  • 基于知识图谱的个性化学习推荐系统的设计与实现_kaic

    摘 要 Abstract 1 绪 论 1 1 研究背景及意义 1 2 国内外现状研究 1 3 研究工作和论文结构 2 相关技术 2 1 HTML 语言 2 2 Python 语言 2 3 数据库技术 2 4 Django 框架 3 系统分析
  • Nvidia Jetson 编解码开发(7)Jetpack 4.x版本Multimedia API 硬件编码开发--输出端对接ROS publish

    1 前言 Nvidia Jetson 编解码开发 6 Jetpack 4 x版本Multimedia API 硬件编码开发 输入端对接Camera V4L2采集 free xx的博客 CSDN博客 基于上篇基于开发 需求 1 2路Camer
  • C语言void指针及使用注意事项详解

    void 指针是一种特殊的指针 表示为 无类型指针 在 ANSI C 中使用它来代替 char 作为通用指针的类型 由于 void 指针没有特定的类型 因此它可以指向任何类型的数据 也就是说 任何类型的指针都可以直接赋值给 void 指针