深入理解指针part1
内存和地址
内存
内存是计算机中的系统资源,它有一个个的单元空间用于储存数剧,每个单元都有一个个属于自己的编号,
举个例子,有一栋学生宿舍有50个房间,你的朋友要去找你,可是他不知道要去哪找你,他就得挨家挨户的找,这样效率很低,可当你给了你的地址,他就能很快的找到你
在计算机中同理,CPU的数据需要去内存中读取,处理完数据后也要回传给内存,当我们买电脑时内存条又8/16/32G的储存空间,可要如何管理内存空间呢?
内存中有一个个的单元空间,每个单元空间都能容纳一个空间的大小
补充:内存中常见的存储单位
内存中的单元空间就像一户,里面可以容纳8个家庭成员,每一个家庭成员都是一bit
每个内存单元都会有编号(和家门前的门派哈一样),有了这个地址,CPU可以快速找到该单元空间
我们通常把门牌号叫做地址,地址在C语言中有个名字:指针
可以理解位内存的单元编号=地址=指针
如何理解编址
在计算机中有很多的硬件单元(显卡,CPU,内存条,主板等),他们之间需要相互协作(至少要能传输数据),可是各个硬件单元都是相互独立的,要怎么让他们互相协作呢?我们要使用“线”把他们链接起来,例如CPU和内存,两者要互相交换数据,所以要用“线”把他们连起来
CPU读取数据时需要访问空间单元时,需要知道空间单元在哪里,可是内存单元很多,所以需要编址(就像一栋公寓里有很多户,所以需要门牌号)
内存编址是在应建设计阶段就设计好的,就像一把吉他,上面并没有标记音阶,可使用者为什么可以在知道那个音在哪条音阶上,因为这把乐器在当初设计是就已经定好哪个地方,使用者再透过学习来熟悉乐器,本质上是约定出来的共识
在计算机中也是同样的道理,在32位元的机器中有32条地址总线,每条线中都有两种状态,表示0或1,一条线有两个状态两条线有四种状态.32条线就有2^32个状态,每个状态代表一个地址
当地址信息被传送到内存,在内存里就能快速的找到该地址里的数据再透过地址总线回传到CPU的寄存器
指针变量和地址
理解了内存和地址的关系后,我们得知道地址是如何
取地址操作符(&)
我们得知道在C语言中,创建一个变量时就是在向内存申请一个空间
我创建了a并在里面存放了20,当中的每个字节都有自己的地址
0x000000C0DDB6FB34
0x000000C0DDB6FB35
0x000000C0DDB6FB36
0x000000C0DDB6FB37
可要怎么知道a的地址呢?这个时候就要说取地址操作符
取地址操作符(&)是专门读取数据所在的单元空间的地址,例如上面20的地址,我们先将他打印出来
注:打印地址的时候要使用%p
int main()
{int a = 20;printf("%p", &a);return 0;
}
从&a取出的地址是四个字节中最小的地址,虽说只打印出最小的地址,但要顺藤摸瓜找其他地址也是可行的
指针变量引用操作符(*)
我们透过取地址操作符取得数据所在地址的数值,例如:0x000000C0DDB6FB34,那我们要怎么讲数据储存起来然后再使用,那我们要把地址放在哪?存放在指针变量中
int main()
{int a = 20;int* pa = &a;return 0;
}
指针变量也是变量,pa就是专门来储存地址的,只要存放在指针变量中的都是地址
当我们了解了存取之后,我们所看到的int*又是啥?
这里我们可以拆分位int 和 * ,我们可以这么理解,int是数据类型,*则是表示 pa 为指针变量
int a = 20; //用于储存变量
int* pa = &a; //用于储存指针变量
解引用操作符(*)
当地址储存在指针变量中后,我们会需要将他们拿出来使用,就像东西放在仓库中,有一天要拿出来使用,数据也一样,这时候就要说解引用操作符(*)
解引用操作符是用于读取地址中数据,在C语言中,只要有地址就能透过地址找到相对的数据,我们来写一段代码
int main()
{int a = 100;int* pa = &a;*pa = 0;return 0;
}
上述代码使用了解引用操作符,*pa是透过pa存放的地址找到存放数据的空间,所以*pa就是a的变量,pa不过是将a变成0而已
指针变量的大小
我们前面了解到32位元的机器有32根地址总线,同理,64位元就有64根地址总线,我们将每一根地址总线当作2进制的序列当做一个地址,一个字节为8个bit,那32根线就有32个bit就有4个字节,64根地址线有64个bit就有8个字节,一个我们先写一段代码:
int main()
{int a = 40;int* pa = &a;char c = 's';char* pc = &c;printf("%zd\n", sizeof(pa));printf("%zd\n", sizeof(pc));return 0;
}
这段代码分别为使用x64(64位元)和x86(32位元)来运行
x64:
x86:
我们发现不论int 或是char 在x64都为8位元在x86都为4位元,为啥?这是因为指针变量和类型大小无关,只要是指针变量,在同个平台下数字都是相同的
指针变量类型的意义
指针的解引用
我先编一个地址,地址为0x15474577,我们来写几段代码
int main()
{int a = 0x15474577;int* pa = &a;*pa = 0;return 0;
}
int main()
{int a = 0x15474577;short* pa = &a;*pa = 0;return 0;
}
我们发现第一段代码四个位元都变成了0而第二段代码只变了两个数字,这是为啥?我使用int类型的数据储存地址,int类型的指针变量有四个空间,所以全部的数据都能被改动,可是short类型的指针变量只能访问两个字节空间所以只有两个数据被改动,所以我们得知每个指针数据类型的权限是不同的
指针+-变数
我们来写一段代码
int main()
{int a = 10;short* pb = (short*)&a;int* pa = &a;printf("%p\n", &a);printf("%p\n", pa);printf("%p\n", pa + 1);printf("%p\n", pb);printf("%p\n", pb + 1);return 0;
}
我们发现pa+1地址跳了四个,pb+1的地址只跳了两个,这是为什么?
因为这里的的+1是指加一个指针变量类型的空间(当然也能-1),int占了四个字节而short只占两个字节,所以int才从8跳到C,而short只从8跳到a
结论就是,每向前和后退一次的距离是有指针的数据类型决定的