【C语言】深入理解指针(1)

目录

前言

(一)内存与地址

从实际生活出发

地址

 内存

 内存与地址关系密切

(二)指针变量

指针变量与取地址操作符

指针变量与解引用操作符 

指针的大小 

指针的运算

指针 +- 整数

 指针-指针

 指针的关系运算

指针的类型的意义

 void* 指针

 const修饰指针

野指针

野指针成因

 如何规避野指针


 

前言

         C语言是一种直接操作内存的编程语言,我们可以直接访问和操作计算机内存中的地址空间。

 

        而C语言中存在的指针类型,指针指向的就是内存中的地址。我们可以通过指针来访问和修改内存中存储的数据。

 

         因此,深入理解指针,并且理解内存,对于编写高质量的程序和调试程序故障都非常有帮助。


 在理解指针之前,我们先引入内存地址的概念:

(一)内存与地址

从实际生活出发

地址

        从实际生活中的问题出发——假如你要去上课,但是学校里教学楼那么多,如果想要上课,就得挨个教室去找,这样当你找到上课的教室,估计也下课了。

        在实际生活中,我们是如何解决找东西效率低这一问题呢?

        ——我们可以将东西排好序,提前熟悉它们的位置,甚至编好号。

        我们将每栋教学楼的每一个房间一一编号,这样,你要去上课,只要有了楼号和房间号,就可以快速找到房间,找到教室。

于是,C语言借鉴生活中的实例,从而有了地址的概念。 

 内存

        我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数 据也会放回内存中。

        我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢?

        其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。

         一个字节有多大呢?

常见内存单位

1byte(字节) = 8bit

1KB = 1024byte

1MB = 1024KB

1GB = 1024MB

1TB = 1024GB

1PB = 1024TB

 (一个内存单元的大小取为1字节是比较合适的,如果取得过大,那么计算机的内存就会小很多,如果过大,那么内存无法被高效利用)

 内存与地址关系密切

        每个内存单元也都有⼀个编号(这个编号就相当于教学楼的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。

        生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。

        C语⾔中给地址起了新的名字————指针

所以我们可以理解为:

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


(二)指针变量

指针变量与取地址操作符

        我们对内存和地址有了初步的了解,我们再回到C语言,在C语言中创建变量其实就是向内存申请空间:

#include <stdio.h>
int main()
{int a = 10;return 0;
}

 

⽐如,上述的代码就是创建了整型变量a,内存中申请4个字节,用于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:

0x007DF784
0x007DF785
0x007DF786
0x007DF787

        上图中,出现了一个陌生的操作符—— &(取地址操作符)

        作用:取出变量的地址

 比如,我们可以将a的地址放在pa中,并用%p  打印出来:

但是,我们发现打印出的只有一个地址, 并且与第一次看到的a的地址不同!

原因在于:

        1.每一次运行代码,编译器都会重新给变量分配内存空间,这也就解释了为什么第二次打印的地址与第一次不同。

        2.同时虽然整型变量占用4个字节,我们只要知道了第⼀个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。

         在上图中,又出现了一个陌生的操作符

指针变量与解引用操作符 

        我们通过取地址操作符(&)拿到的地址是⼀个数值,如:0x006FFD70,这个数值有时候也是需要 存储起来,⽅便后期再使用的,那我们把这样的地址值存放在哪里呢?

        答案是:指针变量中。

        我们可以通过创建一个指针变量,来存储变量的地址:

#include<stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0;
}

        *pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a.

        于是,解引用操作符的作用就是通过地址找到对应的变量。


指针的大小 

        我们知道,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。

        如果是32位机器,那么指针的大小是4个字节。

        如果是64位机器,指针的大小是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;
}

 如图,演示:

         注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。


既然指针的大小与类型无关,那么指针类型存在的意义是什么?

在讨论这个问题之前,我们先引入指针的运算

指针的运算

 指针的基本运算有三种,分别是:

• 指针+- 整数

• 指针-指针

• 指针的关系运算

指针 +- 整数

#include <stdio.h>
//指针+- 整数
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);for(i=0; i<sz; i++){printf("%d ", *(p+i));//p+i 这⾥就是指针+整数}return 0;
}

 1.数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。

2.指针加减整数,表示跳过当前指针类型的元素的个数。

 指针-指针

//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{char *p = s;while(*p != '\0' )p++;return p-s;
}
int main()
{printf("%d\n", my_strlen("abc"));return 0;
}

 1.图中实现的是strlen函数的模拟实现

2.指针-指针  表示两个指针变量之间的元素的个数

 指针的关系运算

//指针的关系运算
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);while(p<arr+sz) //指针的⼤⼩⽐较{printf("%d ", *p);p++;}return 0;
}

1.指针的关系的比较,实际上就是地址大小的比较。


指针的类型的意义

         1.指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。

//代码2
#include <stdio.h>
int main()
{int n = 0x11223344;char* pc = (char*)&n;*pc = 0;return 0;

 对于n的地址:

 *pc = 0;之前

 之后:

         2.指针的类型决定了指针向前或者向后一步有多大(距离)。

#include <stdio.h>
int main()
{int n = 10;char *pc = (char*)&n;int *pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);return 0;
}

 

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。 

 void* 指针

        在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指 针),这种类型的指针可以用来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的+-整数和解引用的运算。

 const修饰指针

见详解

const修饰指针icon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/134341320

野指针

        概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针成因

1. 指针未初始化

2. 指针越界访问

3. 指针指向的空间释放

3的实例

#include <stdio.h>
int* test()
{int n = 100;return &n;
}
int main()
{int*p = test();printf("%d\n", *p);return 0;
}

 如何规避野指针

1.指针初始

2.小心指针越界

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

4.避免返回局部变量的地址

5.assert断言


完~

未经作者同意禁止转载

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

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

相关文章

新华三数字大赛复赛知识点 VLAN基本技术

VLAN IEEE 802.1Q 交换机端口类型 MVRP协议 VLAN Virtual LAN虚拟局域网。LAN可以是由几台少数家用计算机构成的网络&#xff0c;也可以是数以百计的计算机构成的企业网络。VLAN所指的LAN特指使用路由器分割的网络–也就是广播域。将一个物理的局域网在逻辑上划分成多个广播域…

阿里云效一键部署前后端

静态站点到OSS 阿里云-云效&#xff0c;阿里云企业级一站式 DevOps&#xff0c;可以免费使用&#xff08;会限制人数、流水线数量等&#xff0c;个人项目够用了&#xff09;。相关文章 CI 持续集成 - 阿里云云效 OSS 是对象存储的意思&#xff0c;一般一个项目对应一个 Bucke…

C++作业5

完成沙发床的多继承&#xff08;有指针成员&#xff09; 代码&#xff1a; #include <iostream>using namespace std;class Bed { private:double *money; public:Bed(){cout << "Bed::无参构造函数" << endl;}Bed(double money):money(new doub…

JS逆向-mytoken之code参数

前言 本文是该专栏的第60篇,后面会持续分享python爬虫干货知识,记得关注。 本文以mytoken为例,通过js逆向获取其code参数的生成规律。具体的“逆向”思路逻辑,笔者将会详细介绍每个步骤,并且将在正文结合“完整代码”进行详细说明。 接下来,跟着笔者直接往下看正文详细…

微信小程序调用相机拍摄或手机相册

wx.chooseMedia(Object object) 功能描述 拍摄或从手机相册中选择图片或视频。

【面试】测试/测开(ING)

63. APP端特有的测试 64. 服务异常情况验证 65. 用什么做性能测试 66. Jmeter如何设计测试场景 67. 压测怎么做 69. UI自动化元素定位方法 参考&#xff1a;UI自动化元素定位 70. gpu和cpu有什么区别 71. gpu性能收哪些因素的影响 72. 共享内存&#xff0c;线程安全吗…

系统运维工具KSysAK——让运维回归简单

系统运维工具KSysAK——让运维回归简单 1.基本信息 1.1概述 系统异常定位分析工具KSysAK是云峦操作系统研发及运维人员总结开发及运维经验&#xff0c;设计和研发的多个运维工具的集合&#xff0c;可以覆盖系统的日常监控、线上问题诊断和系统故障修复等常见运维场景。 工具…

TypeScript中的类

TypeScript 类 1.TypeScript中类的意义 ​ 相对以前 JavaScript 不得不用 构造函数来充当”类“&#xff0c;TypeScript 类的出现可以说是一次技术革命。让开发出来的项目尤其是大中项目的可读性好&#xff0c;可扩展性好了不是一点半点。 ​ TypeScrip 类的出现完全改变了前…

(C语言)判定一个字符串是否是另一个字符串的子串,若是则返回子串在主串中的位置。

要求&#xff1a; &#xff08;1&#xff09;在主函数中输入两个字符串&#xff0c;调用子函数cmpsubstr()判断&#xff0c;并在主函数输出结果。 &#xff08;2&#xff09;子函数的返回值为-1表示未找到&#xff0c;否则返回子串的位置&#xff08;起始下标&#xff09;。 …

在 SQL Server 中备份和恢复数据库的最佳方法

在SQL Server中&#xff0c;创建备份和执行还原操作对于确保数据完整性、灾难恢复和数据库维护至关重要。以下是备份和恢复过程的概述&#xff1a; 方法 1. 使用 SQL Server Management Studio (SSMS) 备份和还原数据库 按照 SSMS 步骤备份 SQL 数据库 打开 SSMS 并连接到您…

项目实战之RabbitMQ死信队列应用

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 文章目录 &#x1f31f;架构图&#x…

自动化巡检实现方法 (一)------- 思路概述

一、自动化巡检需要会的技能 1、因为巡检要求一天24小时全天在线&#xff0c;因此巡检程序程序一定会放在服务器上跑&#xff0c;所以要对linux操作熟悉哦 2、巡检的代码要在git上管理&#xff0c;所以git的基本操作要熟悉 3、为了更方便不会代码的同学操作&#xff0c;所以整个…

Raspberry Pi 2, 2 of n - Pi 作为 IoT 消息代理

目录 介绍 环境 先决条件 - 设置静态 IP 地址 安装 Mosquitto 启动/停止 Mosquitto 配置先决条件 - 安装 mqtt_spy 配置 Mosquitto 配置 Mosquitto - 无安全性 测试 Mosquitto 配置 - 无安全性 配置 Mosquitto - 使用密码身份验证 Mosquitto 测试 - 带密码验证 概括 介绍 在本文…

今天刷basic

一 在kali里边链接这个服务器 ssh -p 25199 rootnode4.buuoj.cn 然后回车 yes 输入密码123456 ls查看发现什么都没有&#xff0c;cd ..返回上一级目录 ls 发现有flag.txt 查看文件得到flag flag{477f20d3-acd3-46e1-b50a-633e58b769c7}

pytest +uiautomator2+weditor app自动化从零开始

目录结构1.0 把设备连接单独移出去了 模块操作代码&#xff0c;有一些流程操作和断言方法 from devices import dv from time import sleep import random from tool.jt import capture_screenshotdef initialization(func):def wrapper():sleep(1)dv.app_stop(com.visteon.…

如何在Linux环境搭建本地SVN服务器并结合cpolar实现公网访问

目录 前言 1. Ubuntu安装SVN服务 2. 修改配置文件 2.1 修改svnserve.conf文件 2.2 修改passwd文件 2.3 修改authz文件 3. 启动svn服务 4. 内网穿透 4.1 安装cpolar内网穿透 4.2 创建隧道映射本地端口 5. 测试公网访问 6. 配置固定公网TCP端口地址 6.1 保留一个固定…

什么是银行卡第三方支付

银行卡第三方支付是什么意思 在现代社会&#xff0c;随着科技的飞速发展&#xff0c;人们的支付方式也发生了翻天覆地的变化。从最初的现金支付&#xff0c;到后来的支票、信用卡&#xff0c;再到现在的电子支付&#xff0c;每一次支付方式的变革都极大地方便了人们的生活。而在…

计算机基础知识64

ForeignKey属性 to&#xff1a;设置要关联的表 related_name&#xff1a; 反向操作时&#xff0c;使用的字段名&#xff0c;用于代替原反向查询时的’表名_set’ related_query_name:反向查询操作时&#xff0c;使用的连接前缀&#xff0c;用于替换表名 to_field:设置要关联的表…

2023年个人工作总结怎么写?工作任务完成自动记录的待办软件

2023年已经接近尾声&#xff0c;不少人已经开始期待新的一年到来了。不过对于大多数职场人士来说&#xff0c;最近还有一项让人头疼的任务需要完成&#xff0c;这就是撰写2023年个人工作总结。 那么年度个人工作总结怎么写呢&#xff1f;其实很简单&#xff0c;年度工作总结一…

直面多云困境,聊聊F5分布式云的破局之道

在数字化浪潮的大背景下&#xff0c;我们迎来了一个万物上云的时代。Gartner的调查数据显示&#xff0c;81%的公有云用户选择两个或两个以上的云服务供应商。对企业来说&#xff0c;充分利用多云网络可以实现业务的成功转型和增长。然而机遇与挑战并存&#xff0c;本文从多云网…