有关指针相关知识(1)

本文详细解释了内存地址的概念,指针变量的使用,包括取地址、解引用、const修饰以及指针运算,还介绍了野指针的危害和避免方法,以及assert断言的作用。
摘要由CSDN通过智能技术生成

由于指针知识较多并且较为重要,所以分几章阐述。

⽬录:
1. 内存和地址
2. 指针变量和地址
3. 指针+-整数
4. const修饰指针
5.指针运算
6. 野指针
7.  assert断言
1.内存和指针:
在说内存和指针这两个点时,我觉得可以举个简单的例子。
就比如我们学校的一栋宿舍楼,有n个房间,每个房间我们可以理解成一个内存单元,一内存单元就是一字节,然后一个房间可以住八个人,也就是8bit。那么我们要进去找人的话,我们只能从一楼往上一个一个找,效率极其低下。但是我们要是给每个房间都编号那么我们就可以直接找到那个人待的房间,这就是内存指针的一种比喻。
  
生活中我们叫别人家的门牌号是地址,计算机里我们也叫这些内存单元的编号是地址,当然计算机里给这些地址取了新的名字:指针。
内存单元的编号 == 地址 == 指针
如何理解编制呢?
CPU在访问内存寻找字节空间时,肯定是按照地址编号去访问的,如果不编号,CPU就不知道去哪里找。但是内存里的字节肯定不可能明码标着地址是吧,那地址是怎么表示的呢,这里就涉及到了计算机组成原理里的一点知识了。
计算机中的编制不是记住每个字节单元,而是通过硬件设计完成的。
CPU与内存进行数据传递等行为是靠的什么呢,这种电子器件之间的那些事你肯定不能想着因为心灵感应吧,其实是用一根一根的线连接他俩的。而不同地方的线执行不同的功能,有着不同的名字,这里我们讨论的是地址总线。
我们可以简单理解32位机器有32根地址总线,每根地址总线都有两态‘0’,‘1’(电脉冲有无)。那么一根地址线可以表示两种含义,两根地址线可以表示4种含义,32根地址线就可以表示2^32种含义,每种含义都代表一个地址。地址信息被下达给内存,在内存上就可以找到该地址的数据,通过数据总线转递到CPU内寄存器中。
指针变量与地址:
取地址操作符(&),在C语言里创建变量其实就是在向内存申请空间。 那么我们如何得到a的地址呢,这时候就得学习取地址操作符了。
#include <stdio.h>
int main()
{
 int a = 10;
 &a;//取出a的地址
 printf("%p\n", &a);
 return 0;
}

&a取地址操作符取的是字节最小的那个地址,但是这不影响,我们找到了第一个字节,后面几个字节就可以顺藤摸瓜的找出来了。

指针变量:我们既然取出来了地址,那么肯定得保存下来方面后面使用,那我们怎么保存呢,这时候就用到了指针变量。指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

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

指针变量也是变量,是用来存放地址的,只要在指针变量里的值都被认为是地址。我们看的出来pa的前面是int *,这里的*是指pa变量是指针变量,int指pa指向的值是int类型的对象。

解引用操作符:*a

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

当我们拿到了地址就可以由地址(指针)找到指针指向的对象。上面第六行代码*pa意思是通过pa里存放的地址找到pa指向的对象,也就是a。当然肯定会有很多人跟我有一样的疑问:我们之前想修改一个变量的值都是直接a=0,这样不会更方便吗,这里我也不太清楚,待学到后面再来解惑,能知道的就是多了一种途径去修改变量。

指针变量大小:上面说了32位机器会有32根地址线,每根线会发出电信号转为数字信号‘1’,‘0’。那么我们把32根地址线发出的二进制序列当作地址,也就是一个地址是32bit,也就是4字节。而指针变量是用来保存地址的,所以在32位机器里,指针变量大小是4字节,64位机器里指针变量是8字节。

指针的解引用:指针的类型决定了对指针解引用时的权限(一次能操作多少的字节)。char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

指针+-整数:
由此可以看出字符串指针变量加1地址加1字节,整形加1,地址加4字节,所以指针类型决定了指针变量向前或向后一步能走多远。

const修饰指针:
const之前学习过,是用来保护变量不被改变的,使变量变为常变量,当然还是属于变量,但变量不能再被改变。学到指针了你会发现即使用const修饰了变量,但我们只要得到了变量的地址,就可以直接通过地址改变变量的值。 所以这并不合理,因为我们用const修饰变量就想得到一个无法改变它的结果,可现在又有别的方法可以钻空子,那并不好。所以我们得想想办法,下面我们就介绍一下const修饰指针变量的方法
const修饰指针变量:
const可以放在*的左边也可以放在*的右边,意义是不一样的。
int * p;//没有const修饰?
int const * p;//const 放在*的左边做修饰
int * const p;//const 放在*的右边做修饰

const放在*左边修饰时,修饰的是指针变量p指向的对象,指向的对象不可以被改变,但p本身可以被改变。

const放在*右边修饰时,修饰的是指针变量p本身,保证了指针变量p本身无法被修改,但p指向的内容可以被修改。

指针运算:1.指针+-整数  2.指针-指针  3.指针的关系运算

1.指针+-整数:指针加减整数前面提过了。

2.指针-指针:指针减指针的结果的绝对值就是两个指针之间差的元素个数(而不是比特数)。特别的是,当我们用到指针减指针是需要确保两个前提条件,第一点是两个指针必须指向同一个连续的内存区域,第二点是类型需要相同。只有满足了以上两点前提条件,指针减指针才能是有意义的。

3.指针的关系运算:例如:p(指针变量)<arr(数组)。

野指针:

野指针就是指针指向的位置是不知道的(随机的,不正确的,没有明确限制的)。野指针有很大的危害:内存泄漏,程序崩溃,安全漏洞,数据损坏。

野指针出现的三大原因:1.指针未初始化:指针未初始化的话系统会随机赋值。2.指针越界访问:当指针指向的范围超出了操作系统所给范围,它就是野指针。3.指针指向的空间被释放。

避免野指针:如果知道指针要指哪块空间要明确初始,不知道的话就直接给指针赋值NULL。初始化如下:

#include <stdio.h>
int main()
{
 int num = 10;
 int*p1 = &num;
 int*p2 = NULL;
 
 return 0;
}

小心指针越界:一个程序向内存申请了哪些内存指针就只能访问那些空间,不能超出范围,不然就是越界。

指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性。
assert断言:assert.h头文件定义了宏assert(),用于检查运行时程序是否符合指定条件,如果不符合就报错终止程序,我们把这个宏叫做断言。     
assert(p != NULL );
当程序运行到这行代码时,验证p是否等于NULL,如果不等于就继续执行后面的代码,如果等于就终止程序。
assert()宏接收表达式,如果表达式为零,终止程序报错,如果为真,则继续执行。
assert()对程序员很友好,首先它可以自动标识文件和出错行号,其次它有,无需更改代码就可以assert()使用与否的机制,如果确定了没有错误,只用在#include <assert.h>前面加上宏NDEBUG
#define NDEBUG
#include <assert.h>

再重新启动程序,程序就会禁用所有assert()语句。当我们又要使用的时候,只要把宏NDEBUG注释掉就行。

当然assert()也是有缺点的,它的缺点是引入了额外的检查,增加了程序运行时间。一般我们会在Debug版本中使用,在release版本中选择禁用即可。在Debug中使用有利于程序员排查错误,在release中被优化掉,不影响用户运行效率。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值