指针
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针"。意思是通过它能找到以它为地址的内存单元。 如上图中,内存中每一个内存块都有唯一一个编号(地址)做标识。即通过内存块的地址可以找到内存单元。 ::: tip 指针是一个变量,存放内存单元的地址。(可以认为指针就是地址) 存放在指针中的值都被当成变量处理。 ::: 计算各种指针类型的大小:
int main()
{
printf("%d\n", sizeof(char*)); //8
printf("%d\n", sizeof(short*)); //8
printf("%d\n", sizeof(int*)); //8
printf("%d\n", sizeof(double*)); //8
return 0;
}
指针类型算出变量大小为8。
内存的编址
::: tip 一个字节对应一个地址。 1个字节(byte)=8比特(bit)。比特即二进制的0和1。 ::: 对于32位设备,假设有32根地址线,每根地址线在寻址时产生一个电信号正电/负电(数字信号1或0)。32根地址线产生的地址序列就是32个全0到32个全1,即产生2的32次方个地址序列,用来作为内存编号。32个比特位即4个字节,就是32位平台存储一个地址需要的空间。如果是64位平台,产生的64根地址线产生的二进制序列,需要64个比特位的空间,即8个字节。
指针类型的实际意义
1个十六进制位是4个二进制位,2个十六进制位是8个二进制位,8个二进制位是一个字节,即2个十六进制位占一个字节。 ::: tip 1、1个字节 = 8个二进制位 1Byte = 8bit 2、1个十六进制 = 4个二进制位 3、1个字节 = 2个十六进制 :::
int main()
{
int a = 0x11223344; //每2个十六进制数占一个字节,共4字节
int* pa = &a;
char* pb = &a;
printf("%p\n", pa);
printf("%p\n", pb);
return 0;
}
0000000AFE8FF5B4
0000000AFE8FF5B4
上述代码中,pa和pb都是指针类型,8个字节大小,可以存放a的地址。第五行代码将取出的整型的地址放进char*类型的指针变量pb中,编译器会报警告:从“int *”到“char *”的类型不兼容。但编译器仍能将地址存进pb中。
指针类型的意义1
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
int main()
{
int a = 0x11223344;
char* pb = &a;
*pb = 0;
return 0;
}
如上:在存储数据时什么类型的的指针都可以存储。在对指针进行解引用操作时,整型指针可以对4个字节进行操作,字符型指针只能访问到一个字节。 ::: tip 可以理解为: int* p指向int类型,在解引用时就可以访问int类型大小的字节。 double* p指向double类型,在解引用时就可以访问double类型大小的字节。 ::: ::: warning 指针的类型决定了指针进行解引用操作时能够访问的空间大小。 使用指针时应根据解引用时需要操作的范围选择使用合适的类型。 :::
指针类型的意义2
指针+-整数时,指针的类型决定了+-整数时候跳几个字节。int类型时+1需要跳过一个int即4个字节;char类型时-1需要减去一个char即1个字节。
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pb = &a;
printf("%p\n", pa); //000000FCF08FFC84 - pa
printf("%p\n", pa+1); //000000FCF08FFC88 - pa+1
printf("%p\n", pb); //000000FCF08FFC84 - pb
printf("%p\n", pb-1); //000000FCF08FFC83 - pb-1
return 0;
}
::: tip 指针类型决定指针的步长(字节): int* p:p + 1 --> 4 char* p:p + 1 --> 1 double* p:p + 1 --> 8 :::
int main()
{
int arr[10] = { 0 };
int* p = arr; //首元素的地址
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1; //内存中每次向右移动一个int的长度并更改值为1
}
return 0;
}
如下图,循环中每次向右移动一个int的长度并将该位置更改为1,数组中十个元素全部改为1,循环结束:
int main()
{
int arr[10] = { 0 }; //10个元素共40字节
char* p = arr; //*p拿到数组首元素地址
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1; //char类型的指针每次向后更改一个字节的值
}
return 0;
}
如下图,循环中每次向右移动一个char的长度并将该位置更改为1,循环结束时只能更改10个字节的内容,对于int数组来说,字节的改动只涉及到3(2.5)个数组元素。
野指针
野指针是指针指向的位置不可知(随机的、不正确的、没有明确限制的) 野指针的成因有:指针未初始化、指针越界访问、指针指向的空间释放等。
- 指针未初始化:如同局部变量int a不初始化默认是随机值,局部指针变量不初始化,指针变量内的地址就是随机值,可能随机指向内存中的某一空间,对指针变量解引用赋值时,操作是非法的,该指针变量就是野指针。
- 指针越界访问:如下代码中i++到大于10,指针p指向的内存空间超出可以管理的arr数组的范围后,访问非法内存空间,变为野指针。
int main()
{
int arr[10] = { 0 };
char* p = arr;
int i = 0;
for (i = 0; i < 12; i++)
p++;
return 0;
}
- 指针指向的空间释放:如下代码中在自定义函数中创建变量a并将a地址返回到主函数的指针p中,主函数中的指针p成功接收到a的地址时,自定义函数test中的局部变量销毁,即指针p指向的空间被释放,p变为野指针。
int* test() { int a = 10; return &a; } int main() { int*p = test(); *p = 20; return 0; }
规避野指针
::: warning 规避野指针的四个方法: - 1.指针初始化*
- 2.小心指针越界*
- 3.指针指向空间释放后将指针置NULL*
- 4.指针使用之前检查有效性*
:::
指针初始化:
创建一个指针后,当没有想好该指针指向的内存空间时,将指针赋值空指针。
上述代码中的NULL是用来初始化指针的,或者用来给指针赋值。int* p = NULL; //指向空指针,即指向0,如下定义 #define NULL ((void *)0) //把0强制转换为void*类型
小心指针越界: 指针的操作范围控制在允许操作范围内。 当程序出现崩溃,可能出现指针越界,需要检查指针。
指针指向空间释放后将指针置NULL: 指针指向空间被释放后,或者不想让指针指向任何空间时,将指针置NULL,该指针就不再指向任何实际空间了。
指针使用之前检查有效性: 当指针不需要使用置为NULL后,指针没有指向任何有效空间,不能使用该指针。当指针中放的是有意义的地址,才可以使用。即使用指针之前需要判断pa是否等于NULL,如果pa == NULL,该指针就不能使用,程序会崩溃。如下图:(nullptr为空指针)