深入理解指针(一)-3

简介: 深入理解指针(一)

野指针

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


野指针成因

1.指针未初始化



2.指针越界访问

7f107608b204d36c9ed50028961213fc_382c6b1efbbe4e89ae985a07ba95e550.png


3.指针指向的空间被释放

d63f278fe68a5cd308f7c60b4d9f642a_a9b64f705b814510ab55fe9e5dced0be.png


如何规避野指针

1.指针初始化

如果明确知道指针指向哪里就直接赋值地址,如果 不知道指针应该指向哪里 ,可以给指针赋值 NULL .

NULL 是C语言中定义的⼀个 标识符常量 ,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。

3af6f29e38ef0c17fa9ddd49eb5acc40_e0cb4f648c1a4a3f8f595b747a90d23c.png


2.小心指针越界

一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间, 不能超出范围访问 ,超出了就是越界访问。


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

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个 规则 就是: 只要是NULL指针就不去访问, 同时使用指针之前可以判断指针是否为NULL。

33f243dc15a0d3f6c8806e6ba09c3650_d8a44eff919843c6a37e39037a4c0b01.png


4ab2026c2fbb0783cd5b27cb230cad5c_50ddf28a8578451fac813525cfbb1338.png


我们可以把 野指针想象成野狗 ,野狗放任不管是非常危险的,所以我们可以找⼀棵树把野狗拴起来,就相对安全了,给 指针变量及时赋值为NULL ,其实就 类似把野狗栓起来 ,就是把野指针暂时管理起来。

不过野狗即使拴起来我们也要绕着走,不能去挑逗野狗,有点危险;对于指针也是,在使用之前,我们也要 判断是否为NULL , 看看是不是被拴起来起来的野狗 ,如果是不能直接使用,如果不是我们再去使用。


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

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


assert断言

assert.h 头文 件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为 “断言” 。

assert(p != NULL);

上面代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示。


assert() 宏接受⼀个表达式作为参数。如果该 表达式为真 (返回值非零), assert() 不会产生

任何作用 ,程序继续运行。如果该 表达式为假 (返回值为零), assert() 就会报错 ,在标准错误流 stderr 中写入⼀条错误信息, 显示没有通过的表达式 ,以及包含这个表达式的 文件名和行号 。

8e145ecf024801291c054f60c8882e1b_c9faf5c6c9504c8b889eac3e1c4cef19.png

67af6cb7244a6d15cb0c6aa4f860755d_c1d80ac869a2482fad90e0c5c5ef8a86.png


assert() 的使用 对程序员是非常友好 的,使用  assert() 有几个 好处 :

它不仅能 自动标识文件和出问题的行号 ,还有一种 无需更改代码就能开启或关闭 assert() 的机制 。如果已经确认程序没有问题,不需要再做断言,就在 #include <assert.h> 语句的前面,定义⼀个宏 NDEBUG  

#define NDEBUG
#include <assert.h>

然后,重新编译程序,编译器就会 禁用 文件中所有的 assert() 语句。如果程序又出现问题,可以移除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新用  assert() 语句。


assert() 的 缺点 是,因为引入了额外的检查, 增加了程序的运行时间 。

⼀般我们可以在 Debug 中使用,在 Release 版本中选择禁用  assert 就行,在 VS 这样的集成开发环境中,在 Release 版本中,直接就是优化掉了。

这样在debug版本写有利于程序员排查问题, 在 Release 版本不影响用户使用时程序的效率。


指针的使用和传址调用


strlen函数的模拟实现

库函数strlen的功能是求字符串长度,统计的是字符串中 \0 之前的字符的个数。前面用过指针-指针的方式,现在我们换一种方式实现。


8ab3a4084c4af3661343d3cf1dba871a_263051337fdd47889e630a7c975da465.png

如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0 字符,计数器就+1,这样直到 \0 就停止。

传值调用与传址调用


90fafe56ae6d48613b2e8bb49ce219c3_9800048d66d64def990528a5f7e21d09.png

传值调用

9018a9ecefca3bff282687bcb4a728d6_45944acd9e8f4f33a5655a227438e762.png

d69dc3cddac642fd7a75818a7538501c_76b91d0b9a964330b9aead6bafff2afd.png


我们发现,实参a,b的地址与形参 x,y的地址并不相同。所以这里得出一个结论:传值调用函数时,形参只是实参的一份临时拷贝。形参有自己独立的空间,改变形参并不会改变实参


那要怎么改呢?这时就体现出指针的重要性了。


传址调用


04486a5008f134d488e66f38c85a1e2e_33600a5ebce34ad9904a4405eefe91bb.png


传址调用,可以让函数和主调函数之间建立真正的联系 ,在函数内部可以修改主调函数中的变量;所以未来函数中 只是需要主调函数中的变量值来实现计算 ,就可以采用 传值调用 。如果 函数内部要修改主调函数中的变量的值 ,就需要 传址调用 。

相关文章
|
3月前
|
编译器
详解指针(超详细)(第三卷)
详解指针(超详细)(第三卷)
|
3月前
|
搜索推荐 算法 Serverless
详解指针(超详细)(第四卷)
详解指针(超详细)(第四卷)
|
6月前
深入理解指针(一)-2
深入理解指针(一)
|
6月前
|
存储 编译器 C语言
深入理解指针(一)-1
深入理解指针(一)
|
5月前
|
程序员 编译器 C++
指针从入门到精通(一)-2
指针从入门到精通(一)
41 0
|
5月前
|
存储
指针从入门到精通(一)-1
指针从入门到精通(一)
23 0
|
7月前
指针的基础
指针的基础
25 0
|
8月前
|
程序员 C++
基础:第7章:指针
基础:第7章:指针
31 0
|
9月前
|
存储 C语言
指针--基础篇
指针--基础篇
32 0
|
10月前
学C的第五天(初识指针,内存产生内存单元地址过程,指针函数介绍和其大小;结构体补充)-1
13*指针: 13.1 -- 内存: 内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的。 【32位操作系统的内存由32根地址线产生的高低电位(1/0), 划分为2的32次方个地址(编号\指针),一个地址为一个字节, 等于8比特。】