目录
一、指针的变量、内存和地址
1、内存和地址
我们所写的变量,数组这些值都需要开辟空间来存储,而这个空间我们也叫内存,如图:
每一个内存里面有许多的内存单元的比那好,也成为地址,在c语言中也叫指针,而一个内存单元大小取1个字节,不同的变量类型取不同的地址大小,例如:int类型取四个字节,char类型取一个字节,值得注意的是,*(任意)类型在x86环境都是取四个字节,在x64环境取八个字节;一个字节也包含着八个比特位(二进制)。
补充(单位换算):
1 byte = 8 bit;
1 KB = 1024 byte;
1 MB = 1024 KB;
1 GB = 1024 MB
1 TB = 1024 GB;
1 PB = 1024 TB;
2、指针的变量和地址(&和*的使用)
&(取地址操作符)可以快速查找该变量的地址
#include<stdio.h>
int main()
{
int a = 0;
&a;
printf("%p\n", &a);
return 0;
}
运行结果如下:,而这个地址只是a的首元素地址,而它的内存不止这么一点,而他的内存单元是连续存放的,而int占四个字节,所以a的实质地址在006FF28~006FF82C;
对于指针*解引用,主要是和内存配套的,下面举个例子:
int a=10;
int *pa=&a;
我们发现pa的类型是int*,*说明pa是指针变量,而int则说明pa只想的是整形(int)类型的对象;
pa指向的是a的首元素地址,而*pa对a的首元素地址进行解引用,从而找到a的值是10;
二、指针变量类型的意义和使用
1、指针的解引用
int main()
{
int n=0x12345678;
int x=0x11223344;
int *p1=&n;
*p1=0;
char *pc=(char*)&n;
*pc=0;
return 0;
}
对于这个代码,会全部变成0;而x只有前面俩个1变成0;那么为什么会这样呢?因为指针的类型决定了对指针解引用的时候有多大的权限,char*的指针每次只能访问一个字节,而int*指针的解引用就能访问4个字节;
另外对于代码来说,一个字节占八个比特位,代码我们都是以十六进制来编写,所以每一个数字占四个比特位,而0x12345678一共占了32个比特位,也就是4个字节,所以*p1能将n全部变为0,而*pc一个字节只有八个比特位,也就只能把8/32变成0;
2、指针运算
(1)指针+-指针
我们主要是为了观察地址的变化。
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;
}
对于不同的指针类型+1都会跳过不同的直接,int*类型直接跳过4个字节,而char*类型跳过一个字节,当然,指针-1也是往回跳;
(2)指针-指针
对于指针-指针,我们可以得出指针的地址单元差值;
例如:
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
我们可以返回该指针的长度;而这种找长度只对char有效,因为char只占一个字节;
3、void*指针
在指针类型中有一种特殊的类型,也可以理解为无具体类型的指针,可以接受任意类型地址;
局限性:void*类型的指针不能直接进行指针的+-整数和解引用的运算,不然系统会报错,会说因为类型不兼容;
三、野指针
1、野指针成因
对于野指针的成因一共有三种,首先是指针未初始化;
int *p;
指针未初始化的时候默认是随机值,它是不可预估的;
第二种就是指针的越界访问,如下;
#include<stdio.h>
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;
}
当我们对指针进行越界访问的时候,它也会形成一个野指针,是一种比较危险的行为;
第三种则是指针指向的空间释放,已经被释放的指针,里面是没有存放值得,那么指针指向它得时候就也是随机值,如下:
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
2、如何避免野指针
对于三种野指针得成因,我们都是有不同的办法去应对,对于未定义得指针,我们可以定义该指针,如果是不想指向任何一个值,我们可以设置为null值,如下:
#include<stdio.h>
int main()
{
int num=10;
int *p1=#
int *p2=NULL
return 0;
}
而对于第二种我们可以避免越界,不越界的话就不会出现野指针;
第三种我们如果不适用该指针变量得时候,及时设置NULL值,检查有效性,也能够避免
四、const修饰指针变量以及如何使用断言
1、const修饰变量
const是用来限制变量的,也可以理解为把变量变为常量(不能被修改),例如:
int n=0;
n=20;
const int p = 0;
p=20;
n是成功被修改的,而p没有被修改,而且会报错,因为const限制了变量,从而p变成常量不能被修改。
2、const修饰指针变量
对于const限制指针变量可以分为俩部分,一部分是当*放在const左边的时候,限制的是指针指向内容,第二部分则是当*放在const右边的时候,限制的是指针本身。
char const *p;
char * const p;
3、assert断言
assert断言需要包括头文件assert.h;
assert()宏会接受一个表达式作为参数,返回值为真(返回值非0),assert()不会产生任何作用,程序继续运行,如果返回值为假,assert()会报错。对于代码的严谨性,assert()宏的断言还是十分有必要的。