一.指针的概念
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。由于地址是针对内存单元来说的,所以称指针为地址指针。
想要了解指针,我们首先要知道内存的储存是以什么形式的,比如说你创建了一个数组 int arr[10],你可以把这个数组看成一个酒店被分为个房间,房间的门牌号的连续的,有规律的,且一个房间住的人数是固定的。
在32位系统中,这个数组的内存地址是用十六进制数表示的。因为数组中的元素是int
类型,每个int
占用4个字节,所以数组中相邻元素的地址之间的差异是4字节(或在十六进制表示中为0x04
)。这意味着,当我们说某个int
变量(或数组中的某个int
元素)存储在某个地址时,我们实际上是指从这个地址开始的连续4个字节都用于存储这个int
变量的值。
在生活中,如果有了门牌号你才好在酒店找房间,在计算机同理,你把你的指针交给CPU后,CUP才好快速找到你的内存空间。
所以我们可以这样理解,门牌号 == 地址 == 指针。
二.指针变量
(1)指针变量的定义
数据类型名 *变量名(指针名):比如说 int *a,char *a.
把变量名去掉 得到的就是 数据类型。
int *a的类型是 int*
char* a的类型是char *
这一理解很重要,在后面的学习中会有作用。
(2)指针变量的使用
&取地址符:当它作为单目运算符代表取操作对象的地址;例如&i后我们拿到了i的地址,然后我们会创建一个指针变量 int*a = &i;这个指针变量里面存储的就i的地址。
*是指针运算符(间接寻址符):当我们用int *a=&i得到了地址,我们便可以用*a来改变i里面的值。假如 i = 10; 我们就可以通过 *a = 20 这种方式把 i里面的值改成 20。
只要知道地址就能通过指针变量来修改你的值,属于是被偷家了。
根据C语言来说变量的创建实质是在内存中开辟一个空间来存储元素,指针变量同样也是变量,会不会和普通的变量一样呢?
根据以往的知识我们知道各个类型的大小不一样
(X64环境下)
char
:1个字节short
:2个字节int
:4个字节(在大多数情况下与X86相同)long
:在Windows和大多数其他系统中都是8个字节long long
(C99标准中引入):8个字节float
:4个字节(与X86相同)double
:8个字节(与X86相同)
那指针变量呢?char* 和 int *这些不同的类型有区别吗?
其实在X64环境下(也就是64位)
如上图在64位环境下指针变量的大小是8个字节。
而在X86(32位环境下),指针变量的大小为4个字节。
三.指针运算
(1)指针的+-整数
#include <stdio.h>
int main()
{
int n = 10;
char* a = (char*) & n;//这里运用了强制类型转换是为了突出对比.
int* b = &n;
printf("%p\n", &n);
printf("%p\n", a);
printf("%p\n", a + 1);
printf("%p\n", b);
printf("%p\n", b + 1);
return 0;
}
从代码的结果来看 我们发现 char类型指针变量+1跳过了一个字节(第二行第三行结果看出),而int类型跳过了四个字节(第三行第四行结果看出)。
(2)指针+-指针
这一部分,想让大家思考简单点的话,就用门牌号举例。
指针-指针
我们知道 如果给你一个门牌号假如是408,再给你一个门牌号402,由于门牌号有连续性,所以我们能通过这两个门牌号,看出来出来他们相隔6个房间,但前提是同一个酒店,而且这个酒店每一层楼都是一样的规模,如果你朋友给你 A酒店的 408 和 B酒店的402,那不就嗝屁了。
在内存中同理,给你两个地址,您能算出他们之间有相差几个字节,但前提是必须是同一块内存空间。
那么指针+指针呢?是不是同理呢?
很遗憾不是
直接进行两个指针之间的加法,是没有定义的行为,并且编译器通常会报错。难道给你两个门牌号相加,能扩大酒店规模吗?这样理解我相信你一定会记得很深刻的。
(3)指针的关系运算
指针之间可以进行关系运算,这些关系运算通常用于比较两个指针是否指向同一块内存区域中的相对位置。关系运算符包括 >
, >=
, <
, 和 <=
。但是,要注意的是,这些关系运算符只能用于比较指向同一数组(或连续内存区域)内部的指针。
#include <stdio.h>
int main() {
int array[5] = {1, 2, 3, 4, 5};
int *ptr1 = &array[1]; // 指向array[1]
int *ptr2 = &array[3]; // 指向array[3]
if (ptr1 < ptr2) {
printf("ptr1 在 ptr2 之后.\n");
}
if (ptr1 > ptr2) {
printf("prt1在ptr2之前 \n");
}
// 注意:以下比较是未定义的,因为ptr3不指向array中的元素
int *ptr3 = (int*)0xDEADBEEF; // 假设这是一个随机地址
// if (ptr1 < ptr3) { ... } // 不要这样做!
return 0;
}
4.野指针
(1)定义
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
(2)出现野指针的情况
第一种情况是指针变量没有初始化。
在创建一个指针变量时,我们通常给它设置为null或者传一个地址给它,如果没有给它进行这种操作,指针所指向的位置可能是随机值。
第二种指向被释放的内存空间。
调用free或delete释放空间后,指针指向的内容被销毁,空间被释放,但是指针的值并未改变,指向了被释放的垃圾内存。
第三种指针超越变量作用域
也许这种程序能运行,恰好值也是你想要的,或者接近的,就会误导你以为程序没有错误。
int* getNumber() {
int number = 42; // 局部变量
return &number; // 返回局部变量的地址,但此时number的生命周期已经结束
}
int main() {
int* ptr = getNumber();
printf("%d\n", *ptr); // 可能会输出42,但也可能输出其他值或导致崩溃
return 0;
}
总的来说:野指针是指向已经被释放或无效的内存区域的指针,它可能导致数据损坏、程序崩溃、安全漏洞和难以调试的问题。那么我们怎么规避呢?
(3)野指针的规避
-
初始化指针:在声明指针变量时,确保将其初始化为
nullptr
或NULL
。这可以防止在指针被分配实际内存之前被误用。 -
避免返回局部变量的地址:函数内部定义的局部变量在函数返回后会立即被销毁,其内存空间可能会被其他变量覆盖。因此,不要返回指向这些局部变量的指针。
-
释放内存后将指针置为nullptr:在释放动态分配的内存后,将指向该内存的指针设置为
nullptr
。这可以防止在后续代码中误用该指针。 -
检查指针是否为nullptr:在解引用指针之前,始终检查它是否为
nullptr
。这可以防止在尝试访问无效内存时导致程序崩溃。 -
assert断言,assert断言的作用主要在于在程序中设置检查点,以验证程序运行时的状态是否符合预期.
总之 :养成良好的代码书写习惯,有助于你规避野指针哦。