目录
1.内存和地址
1.1首先关于内存和地址,你可以先构思一个公寓楼,里面有很多房间,如果这个公寓很不友好,没把门牌号整上,这天你要去找你的朋友,这样是不是就很麻烦也没效率。
1.2.怎么理解编址
2.指针变量和地址
2.1取地址操作符(&)
2.2指针变量和解引用操作符(*)
2.2.1 指针变量
2.2.2如何拆解指针类型
2.2.3 解引用操作符(*)
2.3 指针变量大小
3.指针变量类型的意义
3.1指针的解引用
3.2指针+ -整数
4.const修饰指针
4.1const修饰变量
4.2const修饰指针变量
5.指针运算
5.1 指针 +- 整数
5.2 指针 - 指针
5.3 指针的关系运算
6.野指针
6.1野指针的成因
1.指针未初始化。
2.指针越界访问。
3.指针指向的空间已被释放。
6.2如何规避野指针
6.2.1指针的初始化
6.2.2小心指针越界。
6.2.3 指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性
6.2.4避免返回局部变量的地址
7.assert断言
assert的好处:
8.指针的使用和传地址调用
8.1传址调用
2.那如果我们把门牌号搞上那是不是就方便多了,很快就能找到你的朋友。相同的计算机中的 内存就是很像这样一个公寓楼,CPU在处理数据时,它要的数据也是从内存中找到的,将数据处理好后,又将数据送回他的地址房间中。
3.其实内存中也是划分好一个个内存单元的,每个内存单元就是1 个 字节(byte)。
4.下表是内存换算单位呢。
1b yte = 8b it1 KB = 1024b yte1 MB = 1024 KB1 GB = 1024 MB1 TB = 1024 GB1 PB = 1024 TB
5.每个内存单元中,就是一个字节(byte)中,想象成是一个宿舍还是个八人间,其中的 8 个 比特(bit)。然后每个内存单元字节是有编号的,就是有门牌号的意思,有了门牌号,cpu就能很快找到想找的内存空间 。
6.门牌号就是 内存单元编号 = 地址 = 指针。指针就是地址!!!
1.在32位系统中 内存和cpu中有一条地址总线是相连接的,地址总线中有32根小线(64位就有64根),每根线只能表示两个形态 1 或者 0.这样32根线就可以表示2 ^32种含义,地址的信息就被下达给了内存,在内存中就可以找到对应的数据,再将数据通过数据总线传输到cpu中
在c语言中创建变量就是向内存申请了空间。如果我们创建了一个 int a = 0变量,要怎么找到他的地址并且利用他呢,就要用到&符号,这个符号可以取出变量a的地址,因为a的类型是int 所以占4个字节,前面的0x十六进制数字就是a的地址,房间号。但打印出来的地址是006FFD70,因为只要有了第一个字节的地址,就可以顺藤摸瓜找到后面的字节的地址。
顾名思义指针变量,就是变量的类型是指针(地址),可以把一个地址储存在一个变量中,这样就方便之后的使用。(因为指针的英文是pointer,所以这里使用p当指针变量)
记住存在指针的值都是地址。
int a = 0;
int* p = &a
首先int a = 0中,int为变量类型,a为变量名称,=0是将0赋予a。
那同理在 int* p 中 int*为变量类型(所以指针类型的书写就是int*),那p 就是变量的名称,=&a就是把a 变量的地址赋予 p ,p里存着 a 变量所在的地址!!
相同的如果想要存的不是 int 类型的变量,那同理也可以 存 char类型的变量,只要把int*改为char*即可。
没错解引用操作符也是* 和乘法一样。
那它存在的意义是什么呢,在生活中我们使用地址找到房间,要用钥匙开门去里面取东西或存放。
c语言中也是同理,一个指针中存放了一个地址,那这个地址里的东西我们要怎么取出来并且更改呢?
这里就要用到解引用操作符(*)
int a = 100;
int* p = &a;
*p = 0;
printf("%d ",a);
像上述代码,*p 就代表着 有人照着 p 中给的 地址,找到了 a房间,并且还把里面的100块抢了,所以现在a 的值就变为了0。*p 就是 一把钥匙,可以进入房间拿东西存东西都可以。
指针变量的大小是固定的不论你要的类型是char* int* long*.....这些类型在32位系统下都是4个字节
在64位中都是8个字节。这是为什么呢?
还记得刚开始我们说的地址总线吗?因为32位系统中可以提出32根线,32个bit位(比特),就需要4个字节来存,因为1个字节等于8个比特位。
相同的平台下,大小都是相同的。
既然指针变量的大小与类型无关,那只要是指针变量,同一平台下,大小都一样,为什么还要各种各样的指针变量呢?那肯定是有意义的!!
观察下列两个代码。一个是int* 一个则强制转换为了 char*。0x的意思是16进制数字。
最后输出我们可以看到,第一个n的4个字节全部改为了0,而第二个强转为char*的n 只有一个字节被改为0
指针的类型决定了,对指针解引用时有多大的权限(一次能操作多少个字节)。如下char*只能修改一个字节,而int*就是4个字节。
//代码1
#include
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}
//代码2
#include
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
先看如下代码,调试观察地址变化。
#include
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
结果如下
可以看出char* 跳过一个字节,而int* 变量+1跳过了4个字节。
所以指针的类型决定了指针+- 时所走过的一步是多大
变量是可以修改的,如果把变量的地址交给一个指针变量,那通过指针也是可以将变量修改的。
但如果利用const就给变量上个锁,让他不能更改。
#include
int main()
{
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}
上述变量中n 是不能直接修改的,因为const给 n 变量上了一把锁,钥匙还不见了。导致n不能改
但是如果我们绕过 n 变量的大门,而是从窗户溜进去也是可以的,但怎么溜进去呢?,我们直接跳过n,利用n的地址(窗户),就可以修改n了,但这样做其实是在打破语法规则。
#include
int main()
{
const int n = 0;
printf("n = %d\n", n);
int*p = &n;
*p = 20;
printf("n = %d\n", n);
return 0;
}
这里看到n的值还是被修改了,这样打破了const的语法规则,既然const就是为了将 n 保护起来才给他上了锁, 所以应该让 n 的窗户(地址),也被保护起来才行。const 病态的爱。那我们要怎么做呢?来帮助这个病态const。
#include
//代码1
void test1()
{
int n = 10;
int m = 20;
int *p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test2()
{
//代码2
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test3()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
void test4()
{
int n = 10;
int m = 20;
int const * const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
int main()
{
//测试⽆const修饰的情况
test1();
//测试const放在*的左边情况
test2();
//测试const放在*的右边情况
test3();
//测试*的左右两边都有const
test4();
return 0;
}
用4个test来测试const放在指针不同位置的时候,对指针的修饰效果。
第一个没有const当然没有任何影响的。
第二个在* 的左边加了const,发现*p (解引用p)是错误的。这就说明了当 const在 * 左边时,const 给 n 上了一个锁,并且把窗子也强化了,所以n 就不能被更改了。
第三个 在* 右边 加了const,发现p = &m,出现了错误,这就说明了,这次不是给 n 上锁了,反而是给 p 这个指针变量上了锁。给他保护了起来,那 p 里存放的地址就不能更改了。
那就说明了 只有 p 里的内容不能更改,但是 n 是没有保护起来的,n 的值还是可以更改的。
第四个在两边都加上了const,那就是 n 和 p 都被上锁,都被 保护了起来,这样两个变量的内容都不能被更改。
总结:如果const 在 *左边,那说明他上的锁是在 p 指针 指向 地址里的内容。那个变量的内容不能更改。
如果 const 在 *右边,那就是p 指针被 上锁了,p 里 存放的内容是不能更改的
指针基本运算一共有三种,分别是:
我们知道数组是在内存中连续存放的,只要知道了第一个元素的地址,就可以顺水推舟的找到剩下的元素。
这里搞了一个数组1 - 10,然后用指针变量p 指向了数组的首元素的地址(&arr[ 0 ])
当然数组的首元素地址也可以直接用数组名。
然后求出数组的里有多少个元素 sz 。
for循环遍历0-i 。输出 *(p + i)的意思是 p是一个指针对吧,那他指向的是个地址,先在给那个地址加个i
记住指针变量的 +- 是根据你定义的指针类型的 int*的话跳过4个字节,char*是一个字节。
所以 p + i 就等于 arr[ i ]。在将其解引用(*)就可以输出数组内容。
#include
//指针+- 整数
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
#include
int my_strlen(char* s)
{
char* p = s;
while (*p != '\0')
p++;
return p - s;
}
int main()
{
char arr[4] = "abc";
printf("%d\n", my_strlen(arr));
return 0;
}
可以自行去调试器中查看结果,结果就是3,所以 指针减指针 算出来的就是 两个指针间(地址间)元素个数。
//指针的关系运算
#include
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
指针的关系运算,可以想成就是地址间的比较,因为地址是一个16进制数字,所以就像是两个数字的关系运算。
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)注意野指针如果指向一个病毒的地址,你解引用时可能就把一个病毒给解引用出来。所以要注意。
即你直接赋予指针,但没给他任何地址(房间号)给他找,那他只能随便找一个房间住着
#include
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
就比如一个数组,给员工开了10个房间,p 是老板,他根据房间找了 每个员工,可这一层还有许多房间,p 老板忍不住,打开了其中一间,结果里面有一伙罪犯,老板直接gg。所以这就是野指针,很危险。
#include
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
这里的代码的意思是,*p 指向的是 一个 函数的 返回值,但返回的这个 n 的地址,其实已经没有了,因为n 是一个局部变量,利用完后就没了。所以地址里存的是什么都不知道。
就像 你还是那个p 老板,你刚刚复活了,然后你的员工已经把房间退了,你还去找你的员工,结果进去 又是一伙罪犯分赃,你又被 暗杀了。
这也是野指针。
#include
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
如果明确了指针要指向哪,那就直接赋予指针那的地址,如果不知道就给他赋值一个NULL。
NULL的值是0,0也是地址但这个地址是无法使用的。所以就像你把野指针这只野狗,拴在了一棵树上这样他就不能乱跑了。
#include
int main()
{
int num = 10;
int*p1 = #
int*p2 = NULL;
return 0;
}
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。
当指针变量指向一块区域时,我们可以通过指针访问这片区域,后期不用这个指针,把它拴在NULL时就行,因为有一个规矩,只要是NULL指针就被拴住了,就不去访问了。同时使用指针之前也可以看看它是否被NULL拴着。
不过就算野狗被拴着我们也得绕开走。
和上面的第三个指针类似。
首先要用assert 要有头文件#include
这样就可以用assert 来判断 指针变量是否指向 NULL,如果指向那就终止程序。
#define NDEBUG
#include
assert 一般只在 debug 调试下使用,在release版本中等于没有。不会影响用户使用程序的效率。
学习指针当然是因为有必须使用指针才能解决的问题。
就像写一个函数交换两个数的值。
这个时候如果就必须用到指针才能完成地址内容的交换。
可以看看我的这篇文章
还有这篇关于strlen函数的内容
有很多问题只能用指针解决,所以指针必不可少