指针是什么?
在C语言中,指针是一个变量,其值为另一个变量的地址,即直接指向内存中的某个位置。指针在C语言中非常重要,因为它们提供了直接访问内存和操作内存地址的能力。
简单来说指针里面存放着一把钥匙(即对应目标的地址,本质是变量),通过这个钥匙可以找到对应的家(即内存中的另一个变量)。
指针变量与地址
在c语言中创建变量其实就是向内存申请空间
#include<stdio.h>
int main ()
{
int a = 0;
printf("%p\n",&a);
return 0;
}
这个便是a变量的地址。(&a,这里&为取地址操作符)。
既然如此我们已经拿到了a的地址(即对应家的钥匙)那我们便可以把a的地址存在另一个变量(即一个人的手上)里面。
#include<stdio.h>
int main()
{
int a = 0;
int* p = &a;//取出a的地址并且存储在p中。
return 0;
}
这时候我们便创建了p这个指针变量,类型为int *类型。
指针的大小
既然指针是一个变量,那肯定也有大小,这里简单说说。
在32位机器上,一个地址需要32个bit位,即4个字节存储。
在64位机器上,一个地址需要64个bit位,即8个字节存储.
(注意:指针变量的大小和类型是无关的,只要是指针类型的变量,在相同平台下,大小都是相同的)
问:那各种类型的指针有什么用呢?---请看指针变量类型的意义部分
如何使用指针?
现在我们已经有了p这个指针变量,要如何使用呢?
这里需要学习一个操作符叫做解引用操作符(*)。
* 与&,在某种意义上可以说是产生相反作用的。一个为解引用,另一个为取地址。
*p = 10;
这样我们就通过p这个指针(钥匙)把a里面的值改成了10(意思好比如说p这个小偷通过a家的钥匙进入a家把a里面的东西篡改了,而不是通过a本身把他自己家的东西进行改变。)
指针变量类型的意义
int main()
{
int n = 0x11223344;
int n1 = 0x11223344;
int* p = &n;
char* p1 = &n1;
*p = 0;
*p1 = 0;
return 0;
}
(这里整数类型n和n1用16进制表示是为了方便观察)
通过上面代码和调试结果可以看到指针的类型决定了,程序在对指针进行解引用时的权限大小(即一次可以操作几个字节。)
如:char*的指针解引用只能访问一个字节,而int*的指针解引用就可以访问四个字节
指针运算
指针加减整数运算
int main()
{
int n = 0x11223344;
int* p = &n;
char* p1 =(char*)&n;
printf("%p\n", &n);
printf("%p\n", p);
printf("%p\n", p+1);
printf("%p\n", p1);
printf("%p\n", p1+1);
return 0;
}
从代码结果我们可以知道:
char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针类型差异导致了不同指针向前或者向后走的字节数不同,即访问的内存不同。
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素
#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;
}
指针减指针的运算
指针减指针得到的是两个指针之间的元素个数(前提两个指针要指向同一块空间)
int array[] = {1, 2, 3, 4, 5};
int* p1 = &array[1]; // 指向数组的第二个元素
int* p2 = &array[4]; // 指向数组的第五个元素
int diff = p2 - p1; // 结果是 3,因为p2在p1之后3个元素的位置
输出为3;
但是如果改为return p1-p2;
则输出为-3;
指针关系运算
#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 2 3 4 5 6 7 8 9 10。
(ps:arr(数组名)就是数组首元素的地址,所以下面两种形式等价)
int*p=&arr[0];
int*p=arr;
while(p<arr+sz)
这行代码中arr为数组首元素的地址,表示当p的地址小于arr+sz(,arr+sz
实际上是数组 arr
最后一个元素后面一个位置的指针)时循环打印p指针所指向的内容(即把数组的所以内容都打印然后才会跳出循环。
利用const防止修改指定内容
int main()
{
const int n = 1;
n = 10;
return 0;
}
这里面由于n被const修饰了所以n无法被修改,代码无法被编译过去。但是
int*p=&n;
*p=10;
我们可以通过p去修改n,但是这个是在打破语法规则,因为n加上const就是不想被修改。
const int*p=&n;
如果代码改成这样子那p也无法改变n了。
那const放置的位置有无影响呢?答案是有。
const的放置位置影响
• const如果放在*的左边,修饰的是指针指向的内容(即不能改变),但是指针变量本⾝的内容可变。(通俗来讲就是只能改变钥匙对应的家里面的东西)
• const如果放在*的右边,修饰的是指针变量本⾝(即不能改变),但是指向的内容可以通过指针改变。(通俗来讲就是只能改变钥匙指向的家的地址,从偷一个家变成偷另一个家)
什么是野指针?
野指针,并不是一个正式的编程术语,但它在程序员之间的口头及网络上广为流传。野指针指的是那些没有被初始化或者已经释放了的内存地址的指针。这种指针的危险之处在于,它指向的内存区域是不可预测的,甚至可能是非法的。
野指针的成因
野指针的产生通常有以下几种情况:
-
未初始化的指针: 分配了指针变量但没有明确初始化,它就可能指向任何地方。(会导致访问冲突,系统崩溃)
int *p; // 未初始化的指针 *p=10;
-
已释放的内存: 当我们使用
free
或delete
释放了一块内存后,如果没有将指向该内存的指针设为NULL
,该指针仍然指向原来的地址。或者在函数结束时把变量的地址带出去了,但是变量的地址已经销毁了。int *p = malloc(sizeof(int)); *p = 10; free(p); // 释放内存后,p成为野指针 //或者 int* test() { int n = 100; return &n; }//函数结束之后n自动销毁,但是因为return &n 导致n的地址被赋给了p int main() { int*p = test(); printf("%d\n", *p); return 0; }
-
越界的指针操作: 指针操作超出了变量的作用范围,这也可能产生野指针。
int arr[10]; int *p = &arr[10]; // 越界
arr数组的下标就到9。
野指针的危害
野指针可能导致各种难以预料的问题,包括但不限于:
- 程序崩溃
- 数据损坏
- 难以追踪的bug
- 安全漏洞
如何避免野指针?
-
初始化所有指针: 声明指针时,尽量初始化为
NULL
或有效的内存地址。int *p = NULL; // 初始化为NULL
-
释放内存后置空指针: 释放内存后,立即将指针置为
NULL
。free(p); p = NULL; // 置空指针
-
小心指针运算: 指针运算时要确保不会越界,也就是确保指针只能访问申请的空间,不能超出范围访问,并时刻警惕指针的合法性。
-
检查有效性:
p = &arr[0];//重新让p获得地址 if(p != NULL) //判断 { //... }
assert(p!=NULL);
利用assert断言也是可以在一定程度上避免野指针的。(assert需要包含头文件#include<assert.h>
上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL(c语言中NULL可以近似看成0) 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
如果你想要更多关于C语言编程的内容,请关注我的CSDN博客。下次我将探讨如何区分函数指针,指针数组,数组指针和其他进阶操作。