C语言——关于指针(逐渐清晰版)

为了更好地理解本篇文章的知识内容,读者可以将以下文章作为补充知识进行阅读 :
C语言————原码 补码 反码 (超绝详细解释)-CSDN博客

C语言————二、八、十、十六进制的相互转换-CSDN博客

C语言————斐波那契数列的理解和运用-CSDN博客

目录

1. 内存和指针(地址)

1.1 内存的介绍

1.1.1内存的划分

1.2 指针和地址

2. 指针变量和地址

2.1 取地址操作符&

2.2 指针变量

2.2.1 指针变量的定义

2.2.2 指针变量的类型

2.2.3 指针变量的大小 

2.3 解引用操作符*

​2.4 指针变量类型的意义

3. 指针计算

3.1 指针+-整数

3.2 指针-指针

3.3 指针的关系运算

4.野指针

4.1 野指针的成因和解决方法

4.1.1 指针未初始化

4.1.2 指针越界访问

4.1.3 指针指向的空间释放

5. void指针和assert断言

5.1 void指针

5.2 assert断言


1. 内存和指针(地址)

1.1 内存的介绍

在计算机中,有各种各样的数据,他们的存储需要在内存中划分空间,计算机中的内存空间大小是有限的。如果把数据比作水,内存就是用以承载水的容器,而我们知道在生活中容器的大小都是有限的。因此我们可以 更好地理解内存之于数据的意义。

1.1.1内存的划分

一个整型变量a= 10存储在程序中需要占据4个字节的内存空间大小,而数据的单位是多种多样的,那我们在内存中应该按照何种单位进行空间划分呢?

为了内存空间的高效管理,内存被划分为一个个的内存单元,而每个内存单元的大小为1字节。

其中,一个bit位可以存储一个二进制的0或1,一个字节可以存储8个bit位,即一个内存单元能存储8个bit位。

在内存中存储的数据需要通过CPU的处理,那么CPU又是如何读取这些数据的呢?

1.2 指针和地址

我们打个比方,当我们在入住一个酒店时,服务员会给我们对应的 房号和房卡,这样我们就能快速找到对应的房间。CPU和内存中的数据传输也是同样的道理,他们之间通过很多的地址总线进行连接,每根线只有两态,表示0或1(联想到二进制),那么通过地址总线不同的脉冲组合形成的这样一个二进制数字,就是对应数据的地址编码,即地址。

​在C语言中,我们将这样的地址起名为指针。

所以我们可以理解为:

内存单元的编号 == 地址 == 指针

2. 指针变量和地址

2.1 取地址操作符&

我们在学习scanf函数时知道,scanf函数除格式化字符串以外的参数代表的都是地址。

当我们在创建变量的时候,他会向内存申请空间,我们想知道他具体的地址编号时就需要用到操作符&,示例如下:

 如图创建的整型变量a,通过查看内存,我们知道他的地址即指针为0x00000099588FFB14-0x00000099588FFB17(x64环境下),共四个字节;但如果我们对a的地址进行打印的话(x86环境下,更加便于查看),结果又是怎样的呢?

我们会发现,他只打印了一个地址编号,这是因为一个数据进行存储时,他的内存空间都是连续的,打印的往往是最低的那个地址编号,进而根据数据的内存大小,从低往高访问对应数据。

经过多次尝试我们会发现,每一次变量的地址都是在发生变化的,这是因为在每次运行程序时,操作系统的内存分配情况存在差异,所以分配给变量的具体内存地址是不同的。

2.2 指针变量

2.2.1 指针变量的定义

那么通过取地址操作符&得到的地址我们又该将他存储在哪呢?为了方便提取这些指针的数据,C语言中用指针变量作为他的容器。如:

#include <stdio.h>
int main()
{int a = 10;int * pa = &a;//取出a的地址并存储到指针变量pa中return 0;

此时的pa就是一个指针变量,而他的类型为int *; 

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址

 在C语言中,地址就是指针,指针就是地址。

2.2.2 指针变量的类型

由上我们知道的一种指针变量类型为int *,我们应该怎么去理解他呢?

我们单独看int * pa = &a这段语句可以知道,a为整型变量,pa存储的是a的地址。由此知道:

int 代表pa存储的指针所指向的数据a的类型(整型),* 表明pa为指针变量。

a和pa分别都在内存中划分了属于他们自己的空间。

那么字符类型的变量a,他的地址又该放在上面类型的指针变量中呢?

我们可以进一步推导如下:

int main()
{char a = '2';char* pc = &a;//字符指针pc,类型为char *return 0;
}
2.2.3 指针变量的大小 

在介绍内存中,我们知道地址的编号是由地址总线输出的信号转换得到的,32位机器假设有32根地址总线,他们产生的二进制序列作为一个地址,那么一个地址就是32个bit位,需要4个字节的存储空间,指针变量的大小就是 4个字节。

同理64位机器,假设有64根地址线,一个地址就是64个二进制位组成的二进制序列,存储起来就需要 8个字节的空间,指针变量的大小就是8个字节。

我们可以输出如下的代码进行测试:

#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{printf("%zd\n", sizeof(char *));printf("%zd\n", sizeof(short *));printf("%zd\n", sizeof(int *));printf("%zd\n", sizeof(double *));return 0;
}

在x86环境下,即32位操作系统

在x64环境下,即64位操作系统 

结论:

1. 32位平台下地址是32个bit位,指针变量大小是4个字节

2. 64位平台下地址是64个bit位,指针变量大小是8个字节

注:指针变量的大小和类型是无关的,同样指针类型的变量,在相同平台下,大小都是相同的

2.3 解引用操作符*

那么对于指针变量,他们应该如何使用呢?这里我们将介绍一个关键的操作符——解引用操作符*

 他相当于是一把钥匙,指针变量是对应的地址,指针变量指向的数据相当于被存储在对应的地址,但我们无法直接操作他,因此需要通过钥匙打开这道壁垒,这样我们在不直接使用数据变量时,也能对数据进行相应的操作。示例如下:

#include <stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0;//找到变量a,并通过*打开操作他的权限return 0;
}

 我们会发现,通过解引用,*pa就相当于变量a,我们能够对他进行重新赋值

​2.4 指针变量类型的意义

由2.1中我们知道,指针变量会存储数据空间中最小的地址编号,整型指针变量解引用时,他会向上访问四个字节的内存空间,我们思考一下,如果我们使用字符指针变量对&a进行访问,能得到正确的数据么?

int *整型指针下,我们打印读取的整型变量数据是正确的(十六进制11223344转为十进制为287454020‬)

 当我们使用char *字符指针对整型变量n进行读取时,我们发现,他仅读取了n内存空间中的一个内存单元,数据为十六进制的44,转为十进制为68。

在这里我们可以发现,不同的指针变量所访问的内存空间大小也是不一样的,因此学习指针变量的类型也是十分关键的。

注:在进行地址存储时,指针变量的类型应该和地址的类型相对应,在代码中我们可以看到(char*)&n,由于&n的类型为int *,我们用char *的指针变量接收他,两者类型不同,为了使指针变量pc能够顺利存储n的地址,我们需要对&n进行强制转换,如果不进行强制转换,编译器会发出警告。(编译器会进行隐式转换类型

结论:指针的类型决定了对指针解引⽤的时候有多大的权限(⼀次能操作几个字节)。

如: char* 的指针解引用就只能访问⼀个字节,而 int* 的指针的解引用就能访问四个字节。

3. 指针计算

3.1 指针+-整数

我们观察如下代码的运行结果:

我们可以发现,整型指针变量+1,他的地址跳过了4个字节;字符指针变量+1,他的地址跳过了1个字节。

指针+1,就是跳过1个指针指向的元素。指针可以+1,那也可以-1。

跳过一个指针的空间大小就取决于指针的类型

3.2 指针-指针

此运算的前提条件

  • 参与减法运算的两个指针必须指向同一数组中的元素(或数组最后一个元素的下一个位置)
  • 两个指针必须指向相同类型的数据(即指针变量类型一致)

运算结果

结果是一个ptrdiff_t类型(在<stddef.h>中定义的有符号整数类型),表示两个指针所指向元素之间的元素个数差,而不是字节数差。

 我们观察如下代码:

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int* p1 = &arr[9];printf("%d\n", (int)(p1 - p));//p1 - p为ptrdiff_t类型,%d无法读取,需要强制转换return 0;
}

 指针求差他们的结果就是两个指针之间内存的元素个数,如下图,两个箭头之间的元素有1,2,3,4,5,6,7,8,9。共9个整型元素,故输出9。

3.3 指针的关系运算

我们知道内存的地址编码是从低到高依次排布的,因为指针是可以用来比较大小的。

常见的关系运算符包括:== 、!= 、< 、> 、<= 、>= 。

这些关系符构建的指针关系运算可以作为语句的判断条件

指针的关系运算常用于数组遍历内存区间判断。

int arr[5] = {1,2,3,4,5};
int *p;// 遍历数组:当p未超过数组最后一个元素时继续循环
for (p = &arr[0]; p < &arr[5]; p++) {printf("%d ", *p);
}

4.野指针

概念: 野指针就是指针指向的位置是不可知的(随机的不正确的没有明确限制的
当一个程序存在野指针时,野指针的行为具有不确定性
  1. 可能立即触发程序崩溃(如段错误)
  2. 可能暂时正常运行,但在后续操作中引发错误
  3. 可能修改无关内存区域,导致数据损坏或程序逻辑错误
  4. 可能触发安全漏洞,被用于缓冲区溢出等攻击

4.1 野指针的成因和解决方法

4.1.1 指针未初始化
#include <stdio.h>
int main()
{ int *p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}

局部变量p未进行初始化,此时的p就是野指针。 

解决方法

如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL。NULL相当于给指针变量了一个固定的地方,不再“野”。
NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。
4.1.2 指针越界访问
#include <stdio.h>
int main()
{int arr[10] = {0};int *p = &arr[0];int i = 0;for(i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;}return 0;
}

当指针指向的范围超出数组arr的范围时,p就是野指针 。

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
4.1.3 指针指向的空间释放
#include <stdio.h>
int* test()
{int n = 100;return &n;
}
int main()
{int*p = test();
printf("%d\n", *p);return 0;
}

当程序运行函数test()结束后,由于n为局部变量,他在内存中所占的空间就会被销毁,导致指针p无法指向具体的变量,成为了野指针。

解决方法

1. 指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

2. 如造成野指针的第3个例⼦,不要返回局部变量的地址。

———————————————————————————————————————————

持续更新中

5. void指针和assert断言

5.1 void指针

5.2 assert断言

打怪升级中.........................................................................................................................................

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93230.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SVG 在线编辑器

SVG 在线编辑器 引言 随着互联网技术的发展&#xff0c;矢量图形在网页设计和数据可视化中扮演着越来越重要的角色。SVG&#xff08;可缩放矢量图形&#xff09;因其文件小、无限缩放不模糊的特性&#xff0c;成为了网页设计中常用的图形格式。SVG 在线编辑器的出现&#xff0c…

libpostproc 已经从 ffmpeg 中移除,导致编译 ffmpeg 时没有 libpostproc

今天编译 ffmpeg 时突然发现 libpostproc 不见了&#xff0c;-enable-postproc 也变成了非法的选项。用搜索引擎搜索相关信息找不到一点&#xff0c;于是去 github 看。 从提交记录可以看到 libpostproc 已经被移除了 链接 主线中已经看不到了 libpostproc 这个目录了

基于 Dell PowerEdge T440 搭建的 Proxmox VE 配置 RTX 3060 显卡直通虚拟机、切换直通

基于 Dell PowerEdge T440 搭建的 Proxmox VE 配置 RTX 3060 显卡直通虚拟机、切换直通 文章目录 基于 Dell PowerEdge T440 搭建的 Proxmox VE 配置 RTX 3060 显卡直通虚拟机、切换直通 1. 前言 2. 前提条件 3. 配置步骤 3.1. 启用 VT-d 3.2. 激活 IOMMU 3.3. 添加 VFIO 模块 …

如何解决pip安装报错ModuleNotFoundError: No module named ‘voila’问题

【Python系列Bug修复PyCharm控制台pip install报错】如何解决pip安装报错ModuleNotFoundError: No module named ‘voila’问题 摘要 在开发过程中&#xff0c;我们常常会遇到pip安装包时出现各种错误&#xff0c;特别是在使用PyCharm进行开发时。本文将详细介绍如何解决安装…

[spring6: @EnableWebMvc]-源码分析

源码 EnableWebMvc EnableWebMvc 是用于启用 Spring MVC 的注解&#xff0c;它通过导入 DelegatingWebMvcConfiguration 来加载默认的 MVC 配置&#xff0c;同时允许开发者通过实现 WebMvcConfigurer 接口来自定义部分配置&#xff1b;若需更高阶的控制&#xff0c;则可直接继承…

Jmeter的元件使用介绍:(四)前置处理器详解

Jmeter的前置处理器可以用来在取样器执行前做一些数据准备操作&#xff0c;也需要注意使用的作用域问题。常用的前置处理器有&#xff1a;用户参数、BeanShell预处理器、JDBC预处理器。一、用户参数 【用户参数】与前面介绍过的【用户定义的变量】有相似之处&#xff0c;先来介…

十七、K8s 可观测性:全链路追踪

十七、K8s 可观测性&#xff1a;全链路追踪 文章目录十七、K8s 可观测性&#xff1a;全链路追踪1、Skywalking 初识1.1 为什么需要全链路追踪平台1.2 全链路追踪核心组件及工作原理1.2.1 全链路追踪核心概念1.2.2 全链路追踪工作原理1.3 什么是Skywalking&#xff1f;1.4 Skywa…

2025 Gitee vs. GitLab:全面对比与选择指南

在软件研发持续加速、合规要求日益严格的背景下&#xff0c;选择合适的代码托管平台成为团队数字化能力建设的关键环节。尤其在中国本土市场&#xff0c;Gitee正凭借其深度本地化能力、全面生态整合和开源社区支撑&#xff0c;成为国内团队首选的开发协作平台。 一、Gitee&…

期货反向跟单忌讳问题(一): 不断调整盘手交易规则

在期货反向跟单领域&#xff0c;不少运营者在摸着石头过河的过程中&#xff0c;容易陷入一个致命误区——对盘手交易规则的频繁调整。这种看似“优化策略”的举动&#xff0c;往往会让整个跟单体系陷入恶性循环&#xff0c;最终偏离盈利初衷。期货反向跟单的核心逻辑是&#xf…

Effective C++ 条款07:为多态基类声明virtual析构函数

Effective C 条款07&#xff1a;为多态基类声明virtual析构函数核心思想&#xff1a;当通过基类指针删除派生类对象时&#xff0c;如果基类没有虚析构函数&#xff0c;会导致派生类资源泄漏。因为此时只会调用基类的析构函数&#xff0c;而不会调用派生类的析构函数。 ⚠️ 1. …

C++进阶—C++11

第一章&#xff1a;C11简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。不过由于C03(TC1)主要是对C98标准中的漏洞进行修复&#xff0c;语言的核心部分则没有改动&#xff0c;因此人们习惯性…

把振动数据转成音频并播放

把振动数据转声音并播放 1、实现流程 安装第三方库: pip install numpy==1.23.5 pip install scipy==1.10.1 pip install sounddevice==0.4.6流程: 1、导入振动数据 2、数据归一化到[-1, 1]范围 3、重采样到44.1kHz 4、播放音频 5、保存音频为WAV文件(可选)2、代码示例 …

ServBay 1.15.0 更新,拥抱 Bun Deno 新生态

历时一个月&#xff0c;ServBay迎来了1.15.0的更新。我们始终坚信&#xff0c;一个优秀的本地开发环境&#xff0c;不仅要稳定、高效&#xff0c;更要紧跟技术的演进脉搏。ServBay 的使命是为开发者扫清开发环境配置的障碍&#xff0c;让您能聚焦于创造本身。 本次ServBay 1.1…

Java设计模式-通俗举例

设计模式就像做菜的食谱&#xff0c;告诉我们遇到常见问题时该用什么"烹饪方法"。今天我就用最生活化的例子&#xff0c;带大家轻松掌握23种设计模式的精髓。一、创建型模式&#xff08;5种&#xff09;&#xff1a;怎么"造东西"1. 单例模式&#xff1a;公…

【跟我学YOLO】YOLO12(3)训练自己的数据集

欢迎关注『跟我学 YOLO』系列 【跟我学YOLO】&#xff08;1&#xff09;YOLO12&#xff1a;以注意力为中心的物体检测 【跟我学YOLO】&#xff08;2&#xff09;YOLO12 环境配置与基本应用 【跟我学YOLO】&#xff08;3&#xff09;YOLO12 训练自己的数据集 【跟我学YOLO】&…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博舆情分析实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解微博舆情分析实现 视频在线地址&#xff1…

【C++】手搓一个STL风格的vector容器

TOC(手搓一个STL风格的vector容器) 手搓一个STL风格的vector容器 github地址 有梦想的电信狗 0. 前言&#xff1a;动态数组的工程实践 ​ 在C标准库中&#xff0c;vector容器作为最核心的序列式容器&#xff0c;其设计融合了动态数组的高效性与安全性。本文将通过完整实现…

24. 了解过 webp 吗

总结 一种图片格式 一、什么是 WebP&#xff1f; WebP&#xff08;发音为 “weppy”&#xff09;是由 Google 推出的一种现代图片格式&#xff0c;支持有损压缩和无损压缩&#xff0c;旨在提供更小的文件体积和更高质量的图像显示。 它兼容常见的图片功能&#xff0c;如&#…

【Unity笔记】Unity Camera.cullingMask 使用指南:Layer 精准控制、XR 多视图与性能提升

Unity Camera.cullingMask 使用指南&#xff1a;Layer 精准控制、XR 多视图与性能提升 关键词&#xff1a;Unity、Camera、Culling Mask、Layer 控制、XR 渲染分离、UI 显隐、性能优化 特别说明&#xff1a; 本文为近期项目所遇问题的总结&#xff0c;仅纯文字记录&#xff0c;…

携带参数的表单文件上传 axios, SpringBoot

页面上的表单如上图, 点击确定按钮需要把参数统一传给后端.前端代码:表单的提交方法const submit async () > {const formData new FormData();formData.append("bookName", bookForm.value.bookName);formData.append("author", bookForm.value.auth…