【C语言】初识指针(二)

news2024/11/17 17:40:45

你可以改变你的行为,但改变不了你想要什么——《浴血黑帮》


目录

1、指针类型

1.1指针加减(+、-)整数

1.2指针的解引用

2、野指针

2.1什么叫野指针

2.1.1指针未初始化

2.1.2指针越界访问

2.1.3指针指向的空间被释放了

2.2如何避免野指针


前言:

大家好,我是拳击哥。今天我给大家带来是初识指针第二期。本期主要讲解的是指针的类型,野指针概念,如何避免野指针的情况发生。


上一期回顾:

指针是什么?

  1.  指针是内存中一个最小单元的编号,也就是地址
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

总之指针就是地址,我们可以通过&(取地址操作符)来取出一个变量在内存中的实际地址,并且可以通过指针变量修改这个变量。那么有一程序:

#include<stdio.h>

int main()
{
	short num = 10;
	short* p = &num;
	*p = 20;
	printf("%d\n", num);
	return 0;
}

 输出结果:20

为啥输出20呢,请容我细细道来:

short num = 10;在内存开辟一块空间,这块空间是num的,占两个字节。怎样得到这块空间的地址呢?我们可以通过&(取地址操作符)来得到这块空间的地址也就是num的地址。

short* p = &num;定义一个短整型指针变量p把num的地址赋值给p,意味着指针变量p指向了num的地址。因此我可以对指针变量p进行解引用来修改num的值。

*p = 20; 对指针变量p进行解引用(*),然后把20赋值给*p。此时指针变量p指向的是num的地址,所以20同时也赋值给了num。


通过上面的例子,我们知道了指针变量是用来存放地址的变量。(存放在指针中的值都被当成地址处理)那么有疑问的是指针的一个小的单元到底是多大?(1个字节)如何进行编址?在上一期我讲到了,您可以点击下方链接进去看看:

【C语言】初识指针(一)_拳击哥带你捶键盘的博客-CSDN博客


1、指针类型

C语言中数据是有类型的如整型、字符型、浮点型等等,那么指针有自己的类型吗。准确来说是有的。我们来看一组代码:

#include<stdio.h>

int main()
{
	int num = 0x11223344;
	int* p1 = &num;
	char* p2 = &num;
	printf("%p\n", &num);
	printf("%p\n", p1);
	printf("%p\n", p2);
	return 0;
}

输出三个个相同的随机地址

012FFEB4
012FFEB4

012FFEB4

以上代码证明了指针变量p1和指针变量p2指向的就是num的地址。如果通过解引用p1和p2修改num的值,会发生什么呢,我们来看两组代码:

//代码1
#include<stdio.h>

int main()
{
	int num = 0x11223344;
	int* p1 = &num;
	*p1 = 0;
	printf("%d\n", num);
	return 0;
}

//代码2
#include<stdio.h>

int main()
{
	int num = 0x11223344;
	char* p2 = &num;
	*p2 = 0;
	printf("%d\n", num);
	return 0;
}

代码1输出结果:0

代码2输出结果:287453952

我们可以看到对p1进行解引用修改了num四个字节的值,对p2进行解引用只能修改num第一个字节的值。说明了指针类型是有局限性的,得按照类型来划分。 注意0x11223344是十六进制数对应的十进制数是287454200。

指针类型决定了指针进行解引用操作时,一次性访问几个字节。如果是int*的指针一次访问四个字节,如果是char*的指针一次访问一个字节 。

有一程序:

#include<stdio.h>

int main()
{
	int num = 0x11223344;
	char* p1 = (char*)&num;
	int* p2 = &num;
	for (int i = 0; i < 4; i++)
	{
		printf("%d ", *p1);
		p1++;
	}
	printf("\n%d\n", *p2);
	return 0;
}

输出结果

68 51 34 17
287454020

上述程序,for循环对p1解引用依次输出了四个数,分别是44、33、22、11对应的十进制数68、51、34、17。因为0x11223344在内存中是倒着存放的,所以输出的值也是倒着输出的。

 

对p2解引用直接输出了十六进制11223344对应的十进制数287454200

证明了char*的指针往后自增时访问一个字节,int*的指针访问四个字节。对应了各个类型字节数,因此证实了指针类型是有意义的,是根据数据类型大小来进行访问的步长


1.1指针加减(+、-)整数

指针的加减整数,就是决定指针往前或往后加减多少个字节。加法就是往后访问,减法就是往前访问。比如定义一个整型的变量,我想一个字节一个字节往后访问这时候可以定义字符型指针指向该变量,我想两个字节往后访问这时候可以定义短整型指针指向该变量。

我们来看短整型往后加1:

#include<stdio.h>

int main()
{
	int num = 0x11223344;
	short* p= &num;
	printf("%d\n", *p);
	p++;
	printf("%d\n", *p);
	return 0;
}

输出结果

13124
4386

分别输出的是短整型往后加1前的值和往后加1后的值 ,十六进制3344对应的十进制是13124,十六进制1122对应的十进制是4386。可以看到短整型往后加1访问的是两个字节。

以上是加法,减法也是一样。往前访问几个字节根据指针对应的数据类型来决定。那么访问几个字节,我们认为这是指针的步长。


1.2指针的解引用

详细大家在上述几个例子中已经了解到了指针解引用操作的用法,还是刚才那组程序:

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* p1 = (char*)&n;
	int* p2 = &n;
	*p1 = 0;
	printf("%d\n", n);
	*p2 = 0;
	printf("%d\n", n);
	return 0;
}

输出结果:

287453952

对指针进行解引用得到的值是根据指针的类型来决定的,如果是字符型那只能访问一个字节,整型能访问四个字节。这看我们的用法,不同的地点不同的用法。如果有程序让我们依次访问一个整型的每个字节里面存的值,这个时候就可以用字符型指针来访问了。

总结:

  • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
  • 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节

2、野指针

今天听到一句惨绝人寰骂人的话:“你TM就是一个没有对象的野指针!”


2.1什么叫野指针

概念:野指针就是指针指向的位置是不可知(随机、无方向、无法意料)的。造成野指针的情况有三种,指针未初始、指针越界访问、指针指向的空间被释放了。

2.1.1指针未初始化

有一代码:

#include<stdio.h>

int main()
{
	int* p;
	*p = 20;
	return 0;
}

 出现警告

局部变量指针未初始化指向的地址是随机值,以上代码定义的指针p就是一个野指针。如果我们直接给未初始化的指针解引用并赋值的话,这个值不知道放哪里去了。因为p指向的地址是未知的、随机的。我们应该这样写:

#include<stdio.h>

int main()
{
	int a = 10;
	int* p=&a;
	*p = 20;
	printf("%d\n", a);
	return 0;
}

 输出结果:20

此时指针p指向了a的地址,指针就有了方向不会造成指向的地址是未知、随机的。 这样的话我再对指针p指向的地址里面内容进行修改,就没有问题。


2.1.2指针越界访问

数组有越界访问的情况,指针也是如此,我们来看代码:

#include<stdio.h>

int main()
{
	int arry[6] = { 1,2,3,4,5,6 };
	int* p = arry;
	for(int i = 0; i <= 6; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

输出结果:1 2 3 4 5 6 -858993460

以上情况出现了指针越界的情况,arry数组只有6个元素,而指针p却访问到了下标为6的第7个元素的地址,第7个元素地址里面的值是未知的,因此造成了野指针。


2.1.3指针指向的空间被释放了

有一代码:

#include<stdio.h>

int* test()
{
	int a = 20;
	return &a;
}

int main()
{
	test();
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

输出结果:20 

虽然输出的结果是20,但是这个程序是很危险的 ,会出现警告。因为test函数在返回主函数时,test函数的栈帧已经销毁了a是一个局部变量因此也销毁了。所以test返回的地址也是一个未知的状况,也许返回的地址让指针p恰好得到了20。但是我在前面加上一段代码,就完全打破了指针p与被销毁的test函数中a的关联。

 如果我在解引用指针p前加上一段程序:

#include<stdio.h>

int* test()
{
	int a = 20;
	return &a;
}

int main()
{
	test();
	int* p = test();
	printf("Hellow World\n");
	printf("%d\n", *p);
	return 0;
}

输出结果

Hellow World
13  

printf函数打破指针p与a的关联,如果解引用指针p能正常的输出20就说明test函数还在内存中,反之并不能那是因为test函数在返回给主函数的已经把所有的内存还给了寄存器,也就是test函数的栈帧被回收了

但如果没有另一块栈帧挤掉当前残存在寄存器里面的test函数栈帧,就会像上方第一个代码那样输出20。 所以这就是为啥在对指针p解引用前加上一个printf语句,指针p就输出其他的值。您应该理解了吧。


2.2如何避免野指针

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放,及时置NULL
  4. 避免返回局部变量的地址
  5. 指针使用之前检查有效性

1.指针初始化,指针p1初始化指向a的地址,这就是指针初始化了 ,如下代码:

#include<stdio.h>

int main()
{
	int a = 10;
	int* p1 = &a;//指针初始化
	return 0;
}

给指针初始化就不会造成野指针的情况,因为指针此时是有明确的指向目标的。


 2. 小心指针越界,指针访问,比如2.1.2程序修改后,我令指针p访问的下标范围在[0,6)之间。

#include<stdio.h>

int main()
{
	int arry[6] = { 1,2,3,4,5,6 };
	int* p = arry;
	for(int i = 0; i < 6; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

 输出结果:1 2 3 4 5 6

指针越界跟数组越界相似,指针通过地址并对地址进行解引用来对应的值,数组则是通过下标来获取对应的值。我们只需要不让指针指向到越界的下标,就不会造成野指针。


3.指针指向的空间被释放了,我们应该及时的把指针置为NULL。如果还要对这个指针进行操作,我们可以先对这个指针进行判断看它是否为空指针。如下方程序:

#include<stdio.h>

int main()
{
	int* p = NULL;
	if(p != NULL)
	{
		printf("%d\n",20);
	}
	else
	{
		printf("%d\n", 21);
	}
	return 0;
}

输出结果:21 

上方程序就是判断指针是否为空,然后进行下一步操作。


4.避免返回局部变量的地址,如2.1.3程序中,test函数销毁了,因此test函数里面的局部变量a也随之销毁了。因此返回的值也是无效的。

#include<stdio.h>

int* test()
{
	int a = 20;
	return &a;
}

int main()
{
	test();
	int* p = test();
	printf("Hellow World\n");
	printf("%d\n", *p);
	return 0;
}

输出结果

Hellow World

13

Hellow World打破指针p与被销毁的a的联系,因此证实了指针p此时指向的是被销毁后的局部变量a,此时p是一个野指针。


 5.使用指针前检查指针的有效性,比如我初始化指针为NULL。这个指针为空了,代表着就不可用了。如以下方程序:

#include<stdio.h>

int main()
{
	int* p = NULL;
	*p = 20;
	return 0;
}

指针p初始化为NULL,并且指针未指向任何有意义的数据此时的指针p就不可用,指针p就是一个野指针。


以上就是今天的博客了,下期我们讲解指针运算,指针和数组,二级指针和指针数组。感谢您的观看

 Never Give Up


初认指针(一)

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

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

相关文章

150. 以前编写好能够正常运行的 SAP UI5 代码,几个月后忽然不能运行了该怎么办?

以笔者本套教材为例,每一步骤的源代码都托管在本人 Github 仓库里,每次上传之前,都确保本地测试通过。 但笔者编写过程中发现,之前测试通过的代码,可能几个月之后再执行,就会遇到白屏现象,即应用无法正常加载,或者无法在调试模式下正常加载。 举个具体的例子。本文写…

渗透测试——找寻绝对路径的方法总结

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右…

Nignx部署前端页面

1.在Linux找到nginx的配置 2.使用vim命令打开nginx.conf vim nginx.conf 3.找到server块 将server_name改为服务器ip地址 4.按照原有的location块新建一个同样的location块 如果有多个就新建多个 5.将要部署的前端页面上传到自己知道的linux中的位置 我这里是存放在 data/w…

靶场-DC

文章目录主机发现端口扫描扫描目录登录网页查看users库查看staff库&#xff08;密码正确&#xff09;爆破ssh端口敲门服务利用提权主机发现 nmap -sP 192.168.111.1/24 arpscan -l netdiscover -p 发现除了本机ip&#xff0c;速度快发现目标机的ip&#xff1a;192.168.111.140…

Go 语言变量

变量来源于数学&#xff0c;是计算机语言中能储存计算结果或能表示值抽象概念。 变量可以通过变量名访问。 Go 语言变量名由字母、数字、下划线组成&#xff0c;其中首个字符不能为数字。 声明变量的一般形式是使用 var 关键字&#xff1a; var identifier type 可以一次声…

基于PHP+MySQL新生报到管理系统(含论文)

每年都有大量的新生需要报到,但是很多时候因为是第一次到本校进行报到,不知道具体的报到流程和学校的安排,如果挨个的去通知无形之间会给工作人员增加工作量,目前最好的办法就是开发一套新生报到系统,让新生可以自己去查看具体的工作流程和安排 本系统是基于PHP和mysql来进行开…

九、数据库的备份还原

九、数据库的备份还原 1、归档管理 使用DM Manager&#xff1a; 点击注册的实例连接->右键->管理服务器 点击系统管理->点击配置->点击转换 点击归档配置->归档 使用DISQL&#xff1a; #创建文件夹存放日志文件 mkdir /dm/dmarch#登录disql disql SYSDBA SY…

细胞衰老——酪氨酸激酶抑制剂

20 世纪 60 年代&#xff0c;Hayflick 和 Moorhea 首次引入细胞衰老的概念&#xff0c;以描述正常的人类二倍体细胞株在连续培养后出现的不可逆的生长停滞现象。后来的研究表明&#xff0c;细胞衰老可由端粒缩短或功能障碍、致癌基因激活、DNA 损伤和突变&#xff0c;以及许多不…

外汇天眼:如果你想成为前5%的交易者

如一个交易者在世界5%的交易俱乐部中&#xff0c;那就意味着其账户获得持续的收益&#xff0c;并且达到全球95%的交易者所达不到高度。而我们需要明确的是这些成功交易者是通过小而稳定的收益建立一个账户&#xff0c;并以可控的速度构建来完成的。 那么天眼君给大家抛出一个问…

当 xxl-job 遇上 docker → 它晕了,我也乱了!

admin 和 executor 都单独部署 部署很简单&#xff0c;我就不具体演示了&#xff08;不是主角&#xff0c;没戏份&#xff01;&#xff09; 直接看效果 192.168.8.222 上部署 xxl-job-admin 192.168.8.223 上部署 xxl-job-executor 是不是很简单&#xff1f; 效果也和我们预想…

物联网智慧养老平台解析

在智慧养老解决方案中&#xff0c;物联网技术的应用是核心&#xff0c;无论是老人的定位还是生物波雷达的跌倒检测都是物联网技术的应用。 一、 系统介绍 RTLS的全称是real time location system&#xff0c;是一种基于信号的无线电定位手段&#xff0c;目前国内RTLS行业主要用…

【0109】Linux系统监测工具sysstat介绍

文章目录 1. sysstat介绍2. sysstat安装2.1 从源码编译sysstat2.1.1 为Android设备编译3. CPU状态查看4. IO状态查看5. 进程状态查看6. 线程状态查看7. 系统活动状态查看1. sysstat介绍 sysstat实际上是一个工具箱,这其中包含了好几个工具。它们的介绍如下: 2. sysstat安装…

用DIV+CSS技术设计的餐饮美食网页与实现制作(web前端网页制作课作业)HTML+CSS+JavaScript美食汇响应式美食菜谱网站模板

&#x1f468;‍&#x1f393;静态网站的编写主要是用HTML DIVCSS JS等来完成页面的排版设计&#x1f469;‍&#x1f393;,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等&#xff0c;用的最多的还是DW&#xff0c;当然不同软件写出的…

2022中国5G+工业互联网大会值得关注的那些事

2022中国5G工业互联网大会 2022年11月20日&#xff0c;由工业和信息化部、湖北省人民政府主办的2022中国5G工业互联网大会在湖北武汉开幕。湖北省委书记王蒙徽出席开幕大会。湖北省委副书记、省长王忠林&#xff0c;工业和信息化部党组成员、副部长张云明讲话。湖南省人民政府党…

数商云S2B2C商城积分商城功能如何实现家用电器企业营销价值最大化?

随着数字化商业时代的到来&#xff0c;消费者行为发生了深刻变化&#xff0c;多元化的消费需求不断驱动着品牌营销思维的变革。对于家用电器行业来说&#xff0c;如何顺应消费者的消费行为变化&#xff0c;不断完善整合更新用户需求&#xff0c;应用更智能化的营销手段与消费者…

第四章. Pandas进阶—数据导出

第四章. Pandas进阶 4.7 数据导出 1.导出.xlsx文件 1).语法&#xff1a; DataFrame.to_excel(excel_writer, sheet_nameSheet1, na_rep, float_formatNone, columnsNone, headerTrue, indexTrue, index_labelNone, startrow0, startcol0, engineNone, merge_cellsTrue, encodi…

(续)SSM整合之springmvc笔记(文件上传和下载)(P159-163)

一 .文件下载 ResponseEntity用于控制器方法的返回值类型&#xff0c;该控制器方法的返回值就是响应到浏览器的响应报文 使用ResponseEntity实现下载文件的功能1. 搞一张图片 2. index.html <a th:href"{/test/down}">下载图片</a> 3 .创建控制器Fil…

日本知名汽车零部件公司巡礼株式会社115

株式会社115 业务内容&#xff1a; 拖车用辅助脚、拖车用零件类、特殊车辆用车轴Sub。Assy产品、面向汽车产业的生产设备、面向建设机械的零部件类、面向汽车产业的检查夹具 公司简介&#xff1a; 董事长&#xff1a;佐藤安弘 资本金&#xff1a;4500万日元 员工数&#x…

在线研讨会 | 多说话人语音融合 - NVIDIA NeMo 代码解析

多说话人语音融合 - NVIDIA NeMo 代码解析 多说话人语音融合任务是 TTS 语音合成当中的一个子任务&#xff0c;它是指将两个或者多个说话人的声音进行融合&#xff0c;合成出新的语音的过程&#xff0c;而不需要做任何进一步的微调。多说话人语音融合可以通过插值的方式代替预…

线程创建方式

Thread&#xff1a;线程 创建方式&#xff1a; 方法一&#xff1a;继承Thread类 1. 继承 Thread 来创建一个线程类 2. 创建 MyThread 类的实例 3. 调用 start 方法启动线程 方法2 实现 Runnable 接口 1. 实现 Runnable 接口 2. 创建 Thread 类实例, 调用 Thread 的构造方…