C语言深入理解指针(非常详细)(二)
目录
- 指针运算
- 指针+-整数
- 指针-指针
- 指针的关系运算
- 野指针
- 野指针成因
- 指针未初始化
- 指针越界访问
- 指针指向的空间释放
- 如何规避野指针
- 指针初始化
- 注意指针越界
- 指针不使用时就用NULL
- 避免返回局部变量的地址
- assert断言
- 指针的使用和传址调用
- 传址调用
- 例子(strlen函数的实现)
指针运算
指针的基本运算有三种,分别是:
• 指针±整数
• 指针-指针
• 指针的关系运算
指针±整数
因为数组在内存中是连续存放的,比如int类型的数组,每个元素相差4个字节,因此我们只需要知道首元素的地址就可以通过加减的方式找到后面元素的地址。
int arr[10] = {1,2,3,4,5,6,7,8,9,10}
#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 这⾥就是指针+整数 i每增加1就往后移动4个字节
}
return 0;
}
指针-指针
/指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )//遇到\0就代表字符串结束
p++;
return p-s;//同过着两个指针相减我们可以得到这个指针的总长度
}
int main()
{
printf("%d\n", my_strlen("abc"));
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]);
while(p<arr+sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;//p没加1就增加4个字节
}
return 0;
}
野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的
野指针成因
指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)//i=10和i=11时越界了
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
指针指向的空间释放
#include <stdio.h>
int* test()
{
int n = 100;
return &n;//不是全局变量,在函数结束后地址就会消失
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
如何规避野指针
指针初始化
如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL(空指针,也可以理解为0,但是不完全是0,因为0有整形和char类型,只是有那个意思)
NULL 是C语言中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endi
初始化如下
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = #
int*p2 = NULL;
return 0;
}
注意指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问
指针不使用时就用NULL
当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的一个规则就是:只要是NULL指针就不去访问
因此使用指针之前可以判断指针是否为NULL。
这里就是用if语句判断
int main()
{
int arr[10] = {1,2,3,4,5,67,7,8,9,10};
int *p = &arr[0];
for(i=0; i<10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
//...
p = &arr[0];//重新让p获得地址
if(p != NULL) //判断
{
//...
}
return 0;
}
避免返回局部变量的地址
我们就需要创建的变量不是局部变量,也就是说我们可以创建全局变量,当然你可以在mian函数里面创建变量,然后将变量的地址传入函数中,再通过函数进行一系列操作,结束时可以将变量的地址传出,这样就可以避免返回局部变量了。
assert断言
assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”
可以理解为进行了一次安检,在通过时会对这个变量进行检测,判断是否符合条件
assert(p != NULL);
上面代码在程序运行到这⼀行语句时,验证变量 p 是否等于 NULL 。
如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示。
assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值非零), assert() 不会产生任何作用,程序继续运行。
如果该表达式为假(返回值为零),assert() 就会报错,在标准错误流 stderr 中写入⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。
assert() 的使用对程序员是非常友好的,使用 assert() 有几个好处:它不仅能自动标识文件和出问题的行号,还有⼀种无需更改代码就能开启或关闭 assert() 的机制。
如果已经确认程序没有问题,不需要再做断言,就在 #include <assert.h> 语句的前面,定义⼀个NDEBUG
#define NDEBUG
#include <assert.h>
然后,重新编译程序,编译器就会禁用文件中所有的 assert() 语句。如果程序又出现问题,可以移除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新启用了 assert() 语句。
assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。
⼀般我们可以在debug中使用,在release版本中选择禁用assert就行,在VS这样的集成开发环境中,在release版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在release版本不影响用户使用时程序的效率
指针的使用和传址调用
传址调用
学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?
例如:写⼀个函数,交换两个整型变量的值
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
代码运行入下:

在main函数内部,创建了a和b,a的地址是在调用Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是
x和y确实接收到了a和b的值,不过x的地址和a的地址不一样,y的地址和b的地址不一样,相当于x和y是独立的空间,那么在Swap1函数内部交换x和y的值,自然不会影响a和b,当Swap1函数调用结束后回到main数,a和b的没法交换。Swap1函数在使用的时候,是把变量本⾝直接传递给了函数,这种调用函数的方式我们之前在函数的时候就知道了,这种叫传值调用。因此当我们传入内存后,运行结果如下:

我们可以看到实现成Swap2的方式,顺利完成了任务,这里调用Swap2函数的时候是将变量的地址传
递给了函数,这种函数调用方式叫:传址调用
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。所以Swap是失败的了
例子(strlen函数的实现)
//计数器⽅式
int my_strlen(const char * str)
{
int count = 0;
assert(str);
while(*str)
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
相关文章:
C语言深入理解指针(非常详细)(二)
目录 指针运算指针-整数指针-指针指针的关系运算 野指针野指针成因指针未初始化指针越界访问指针指向的空间释放 如何规避野指针指针初始化注意指针越界指针不使用时就用NULL避免返回局部变量的地址 assert断言指针的使用和传址调用传址调用例子(strlen函数的实现&a…...
Web3j 继承StaticStruct的类所有属性必须为Public <DynamicArray<StaticStruct>>
Web3j 继承StaticStruct的类所有属性必须为Public,属性的顺序和数量必须和solidity 里面的struct 属性相同,否则属性少了或者多了的时候会出现错位 Web3j 继承StaticStruct的类所有属性不能为private,因为web3j 是通过长度去截取返回值解析成…...
Kubernetes(k8s)上安装Prometheus和Grafana监控
Kubernetes上安装Prometheus和Grafana监控 环境准备Kubernetes准备 安装项目开始安装下载安装的项目安装项目替换镜像替换kube-state-metrics替换prometheus-adapter 修改Service修改alertmanager-service.yaml修改grafana-service.yaml修改prometheus-service.yaml 执行这些ya…...
黑马 软件测试从0到1 常用分类 模型 流程 用例
课程内容: 1、软件测试基础 2、测试设计 3、缺陷管理 4、Web常用标签 5、项目实战 以终为始,由交付实战目标为终,推出所学知识;从认识软件及软件测试,到如何设计测试、缺陷标准及缺陷管理,最终以项目实战贯…...
面试中的商业思维:如何展示你对业务的理解
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...
Docker切换文件系统为VFS
一、介绍 Docker支持AUFS、Btrfs、Device Mapper、OverlayFS、VFS、ZFS六种不同的存储驱动。 1. AUFS AUFS是一种常见的存储驱动程序,它也使用了Linux内核的AUFS文件系统。它的优点是支持所有的Linux发行版,可以在不同的容器之间共享文件系统…...
Spring Security存在认证绕过漏洞 CVE-2021-22096
文章目录 0.前言1.参考文档2.基础介绍漏洞影响范围:官方说明:修复版本:漏洞利用步骤:修复方式: 3.解决方案 0.前言 背景:项目被扫到Spring Boot 的漏洞,严格的说应该是Spring Security 组件的漏…...
前端list列表自定义图标并设置大小
前端list列表自定义图标并设置大小 一、前端list列表基础知识回顾 前端公有两种列表,一种是有序列表(ol),一种是无序列表(ul),它们的子元素都是(li)。 1.1 有序列表&a…...
Multisim14.0仿真(五)三角波发生器
一、仿真原理图: 二、仿真效果:...
使用安全复制命令scp在Windows系统和Linux系统之间相互传输文件
现在已经有很多远程控制服务器的第三方软件平台,比如FinalShell,MobaXterm等,半可视化界面,使用起来非常方便和友好,两个系统之间传输文件直接拖就行,当然也可以使用命令方式在两个系统之间相互传递。 目录…...
SOC总线学习记录之ICB(Internal Chip Bus)
蜂鸟E203总线: 采用自定义总线协议 ICB(Internal Chip Bus),该总线用于蜂鸟 E203 内核内部使用,同时也可作为 SoC 中的总线使用。 ICB 总线的初衷是为了能够尽可能地结合 AXI 总线和 AHB 总线的优点,兼具高…...
rabbitMQ手动应答与自动应答
手动应答模式(manual) 解释: 手动应答:既是当消费者消费了队列中消息时需要给队列一个应答,告诉队列这条消息我已经消费了,可以删除了; 若是不应答,即使消费了 队列没收到消费成功的提示 所有消息会一直在队列中; 注意 注意 注意:重要的事情说三遍,下面说的…...
java对象创建的过程
1、检查指令的参数是否能在常量池中定位到一个类的符号引用 2、检查此符号引用代表的类是否已被加载、解析和初始化过。如果没有,就先执行相应的类加载过程 3、类加载检查通过后,接下来虚拟机将为新生对象分配内存。 4、内存分配完成之后,…...
WireShark流量抓包详解
目录 Wireshark软件安装Wireshark 开始抓包示例Wireshakr抓包界面介绍WireShark 主要界面 wireshark过滤器表达式的规则 Wireshark软件安装 软件下载路径:wireshark官网。按照系统版本选择下载,下载完成后,按照软件提示一路Next安装。 Wire…...
【密码学代码分享】突破ECDSA算法封装--JS无三方包纯手写ECDSA
ECDSA(Elliptic Curve Digital Signature Algorithm)是一种基于椭圆曲线密码学的数字签名算法。它用于确保数字数据的完整性和身份验证,通常在信息安全和加密通信中使用。在日常使用中,通常会使用一些函数库来实现完成这个算法的功…...
stable diffusion实践操作-文生图
本文专门开一节写文生图相关的内容,在看之前,可以同步关注: stable diffusion实践操作 正文 1 liblib SD1.5底模 lora(baihuaniang_1.0) 详细信息: 底模:SD 1.5 Lora:baihuaniang_1.0 正向提示词: Best …...
Spring容器及实例化
一、前言 Spring 容器是 Spring 框架的核心部分,它负责管理和组织应用程序中的对象(Bean)。Spring 容器负责创建、配置和组装这些对象,并且可以在需要时将它们提供给应用程序的其他部分。 Spring 容器提供了两种主要类型的容器&…...
# Go学习-Day9
文章目录 Channel声明存入取出一个简单的死锁分析 个人博客:CSDN博客 Channel Channel本质是一个队列多goroutine访问时不需要加锁,Channel天然线程安全channel有类型,只能写入相同类型channel是引用类型channel必须初始化才能写入数据&…...
chatGPT如何在Java中使用
ChatGPT是一个基于GPT模型的聊天机器人平台,它提供了API接口,可以通过HTTP请求与之交互。您可以使用Java编写程序,通过HTTP请求与ChatGPT进行交互,实现聊天机器人的功能。 具体步骤如下: 1、注册ChatGPT账号并创建应…...
vue+axios——创建多个实例共用请求拦截器和响应拦截器(403错误信息不提示bug解决)——基础积累
创建多个实例共用请求拦截器和响应拦截器:使用的是函数的继承,也就是call()方法,这个方法第一个参数就是this,后面的参数可以是一个也可以是多个。最后一定要记得要return出去,否则接口是拿不到数据的。 import axios from axios…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
