在前面c语言简介的文章中提到,内存单元的大小是一个字节,指针(或者说地址)标记着每一个内存单元。对于32位和64位的机器,他们的编址方式是不同的,对于32位机器:
32位机器中存在着32根地址线,每根地址线可以表示一个二进制位,由此可得,32根地址线可以表示的不同地址的数量是2^32,又因为:一个字节等于8个比特位,而且32根地址线的情况下,1个地址是由32个二进制位来表示的,所以,一个地址的大小就是4个字节。因此,32位机器中,存储一个地址(指针变量)需要4个字节大小的空间
同理,对于64位机器,其可以表示的不同地址的数量是2^64个,存储一个内存单元所对应的地址(指针变量)需要8个字节大小的空间
文章大体内容导读:
目录
1.指针的类型:
在前面指针简介的文章中,只是提及了指针的概念、大小、简单应用,并没有对指针的类型进行展开介绍,和变量有整型,浮点型等。指针也存在不同的类型。
通过下面对不同类型指针的打印,便可以得到不同类型指针所占空间的大小:\
#include<stdio.h>
int main()
{
printf("%d ", sizeof(int*));
printf("%d ", sizeof(short*));
printf("%d ", sizeof(long*));
printf("%d ", sizeof(char*));
printf("%d ", sizeof(float*));
printf("%d ", sizeof(double*));
return 0;
}
(注:计算机是64位计算机)
得到的结果如下:
可以看到,不同类型的指针,在64位计算机的中所占的空间大小都是8个字节,这里就会产生一个问题,既然不同类型的指针所占空间的大小都是一样的,那么设置这些不同类型的指针还有什么实际意义?
1.1 不同指针类型的意义:
1.1.1 不同类型指针访问字节的差异
对于不同类型指针的意义的探讨,将通过下面的若干例子进行说明:
如图, 将十六进制数字11223344(0x代表16进制)赋给4字节大小的a变量,因为在十六进制中,1个十六进制位等于4个二进制位,所以11223344这8个十六进制位就等于32个二进制位,又因为一个字节=8个二进制位,所以可以得出,0x11223344这一串数字,恰好可以占用4个字节大小的空间,也就是可以恰好填满变量a
int main()
{
int a = 0x11223344;
int* p = &a;
*p = 0;
return 0;
}
当对上述代码在内存中进行监视时,可以发现:
(注:此时程序仅仅运行到箭头所指的行所对应的代码)
对变量a赋值的十六进制数字,也就是:11223344,但是可以看到,输入的内容在内存中的存储是倒叙的,对于为什么倒叙,将在后续文章进行解释,本文不说明。
当把上述程序完全运行后,可以通过内存窗口看到:
内存中存储的值全部变成0
接下来,将上述代码中变量a的类型从int改为char,即:
int main()
{
char a = 0x11223344;
char* p = &a;
*p = 0;
return 0;
}
此时,如果再从内存窗口监视上面更改过的代码(此时代码完全运行)
此时发现,当指针变量类型是char时,内存中只有第一个字节所对应的内容变成了00。通过对char和int类型指针变量的对比可以发现:
不同类型的指针,在进行解引用操作时,可以访问的的字节的数量是不同的,例如:对于Int类型的指针,在进行解引用操作时,可以访问4个字节,而char类型的指针在进行解引用时,只能访问1个字节
(注:指针在进行访问时,访问的内容所对应的地址是由低到高的顺序)
对于其他类型的指针,例如short float double 分别可以访问 2 4 8 个字节的内容,因此对于不同类型的指针,其可以访问内存内容的大小 = sizeof(type) (其中type代表不同的类型)
1.1.2 不同类型指针进行整数加减时的差异
上面已经通过例子探讨出不同类型的指针变量的一个意义——不同类型指针变量可以访问内容的大小是不同的,下面,将通过另一个例子来探讨不同类型指针变量的另一个意义:
(注:为了方便查看指针地址的变化,下列代码将在x86环境下进行运行)
int main()
{
int a = 0x11223344;
printf("%p ", &a);
printf("%p ", &a + 1);
return 0;
}
通过对上述代码对a和a+1的地址进行打印,可以得到下面的结果:
即a的地址是: 00EF9FC
a+1的地址是:00EFFA00,由十六进制的运算可以得到,上面两个地址的差值恰好为4.
对上述代码进行更改,即把变量a的类型由Int改为char
int main()
{
char a = 0x11223344;
printf("%p ", &a);
printf("%p ", &a + 1);
return 0;
}
执行上述代码,可以得到下面图中的结果:
此时,a的地址是: 001CE823
a+1的地址是:001CF824
两个地址之间的差值是1.
对比两种类型的指针变量的地址所对应的结果,得到不同类型指针变量的另一个性质,即:
不同类型的指针,决定了指针进行+、-n操作时的步长。
对于int整型变量,进行加n操作时,可以跳过4n个字节
对于char类型变量,进行加n操作时,可以跳过n个字节
所以,对于不同类型的变量进行+、-n操作时,可以跳过的字节= n * sizeof(type)
2.野指针:
2.1 指针未初始化:
给定如下代码:
int main()
{
int* p;
*p = 5;
printf("%d", &p);
return 0;
}
如果对运行上述代码,会发现编译器显示如下错误:
这是因为,上述代码并没有对指针变量p进行初始化,对于未初始化的变量,编译器会给其赋随机值。这种未初始化的指针,就叫做野指针 ,也可以理解为,指向位置是不可知的指针。
2.2 指针越界访问:
给定下面代码:
int main()
{
int arr[10] = { 0 };
int* pa = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
*(pa++) = i;
}
return 0;
}
假设,用下面的方框代表创建的数组,方框下面的数字代表对数组的编号:
可以看到,在进入for循环后,指针变量pa先进行解引用,再++,也就是指针变量pa先对编号为0的数组进行赋值,赋值后指针变量移向编号为1的数组,再下次循环中,再次解引用,再++,以此类推,但是,当循环到pa加到10的时候,会发现,前面创建的数组的下标范围是0~9,没有编号为10的空间,这就造成了指针越界访问,例如下图 :(红色方框代表超出的范围)
2.3指针所指向的空间释放:
给定如下代码:
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("%d", *p);
return 0;
}
上述代码,想通过test函数返回值返回变量a的地址,再将a的地址赋给指针变量p,再打印p。但是
test函数内部的变量a是一个局部变量,局部变量在函数test运行结束后,就会自动销毁,因此,指针变量*p不能拿到变量a的地址。
2.3 如何规避野指针:
1. 对明确知道指针可以初始化的地址时,及时对指针初始化。
2.对不明确知道指针初始化的地址时,将指针暂时初始化为NULL(即空值),在后续代码书写时,如果得知了该指针可以初始化的地址,对该指针初始化。
3.小心指针越界访问
4.避免返回局部变量
5.在使用指针之前,检查其有效性
3.指针运算:
3.1 指针运算——指针加减整数
在文章探讨不同类型的指针变量的性质时,曾说到过指针加减整数的运算规律,这里不再过多探讨,只给出一个应用:
例:在不适用数组下标的情况下,赋值并打印数组:
代码如下:
int main()
{
int arr[10] = { 0 };
int* pa = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*(pa + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(pa+ i));
}
return 0;
}
在前面介绍数组的文章中曾提到,数组名= 数组首元素的地址,所以,可以创建指针变量pa来存储数组首元素的地址,再通过对指针进行++,完成赋值并打印指针的目的,结果如下:
3.2 指针运算——指针和指针运算:
给定下面代码:
int main()
{
int arr[10] = { 0 };
printf("%d ",&arr[9] - &arr[0]);
return 0;
}
结果如下:
这里直接给出结论:指针-指针得到数值的绝对值的是两个指针之间的元素个数。
但是需要注意:指针-指针的条件是:指针和指针位于同一空间
3.3指针的关系运算
即进行指针之间的大小比较运算,例如:
int arr[10] = {0};
int*p = arr;
arr[5] > arr[0]