[TOC]
1. 两个问题
指针变量
所储存的地址来源于何处?- 地址是相对存在还是绝对存在?
2. 直接解答
- 指针变量所储存的地址是逻辑地址,来源于操作系统分配的内存段或者说进程地址空间。
- 地址是实际存在的,且既是相对存在也是绝对存在。
3. What does it mean?
3.1. 地址指针
在存储器中存取(access)每个字(word)都需要有相应的标识符,将每个标识符称为地址
,所有在存储器中标识的独立的地址单元的总数就是地址空间
。除了物理地址(physical address
),地址空间和其他的概念都不是物理上存在的,而物理地址本身不存储于任何变量的时候,本身也不实际存在于内存之中,就像住的房子肯定是有门牌号但没贴明在某个地方一样.地址作为很特殊的值,当存在于变量中的时候,必须对变量用*
进行声明.如int *p;
.在C语言中把这种专门用来存放变量地址的变量称为指针变量
,简称为指针
(pointer).值得一提的是,在C中,原本指针被认为是一种概念,是计算机内存地址的代名词之一.于是通过指针变量就等于通过变量的地址进行操作,而变量作为内存内容又是存储于内存地址之上的,从而达到了访问内存,使用变量的目的.
指针的概念和用法本身都是容易混淆的,这里整理一下:
3.2. 指针vs引用
引用本身并不是一个对象,而只是一个别名(外号),因此不能定义指向引用的指针;但指针是一个对象,所以存在对指针的引用。对引用的修改便是对这个有着小名的变量的修改,这在函数参数传递中有用:
引用&(变量定义区)和地址&(变量操作区),指针声明和解引用,代表的不是同样东西,只是写法相同.monster_acm
3.3. 指针变量的类型
程序执行时,变量-每一个占8bit的存储单元
中共同存储的二进制数据供程序使用。变量的两个部分之一变量名
对应了一块具体的内存地址,而另一部分变量类型
表明该如何翻译内存中存储的二进制数1:
整型→二进制:数学转化 浮点数→二进制:IEEE st 字符→二进制:ASCII 指针变量→二进制:like unsigned int
因此我们在书写指针变量时,给定类型的意思实际是它指向变量的类型。这个类型决定了如何翻译对应内存的值,以及访问多少个字节的内存。给定类型至少有一个作用就是从这个指针变量所指向的地址开始指明读入什么最终形成二进制数据存储(如scanf()
)或者怎样读出(如printf()
)这个地址后面的二进制数据.值得一提的是,void*
表示不知道指向什么东西的指针,因而规避了对某内存地址上内存内容的翻译,此时可能是有用的:只是指向的某个内存地址可能是外部设备地址.
在给指针变量赋值时,北桥芯片
通过外部总线将内存中的数据送到一级缓存
中,CPU通过内部总线再将变量的地址赋值给临时寄存器
2,然后将寄存器的值赋值最后给指针变量。
3.4. 什么时候用物理地址?
现在OS的问题,就是内核微小的错误都会让整个系统挂掉.这和我们写软件应该用多线程还是多进程一样.为了防止这个问题,除了添加保护级别,或许可以从硬件底层彻底抛弃现在的进程切换方式,毕竟保存的上下文太多.只需要完全使用实地址,但是代码完全无法访问对象外面的内存,只要语言里面没有指针,自然就没法访问不是给你的对象.而为了防止越界,才有了”进程""虚拟地址”这样的概念.王垠
操作系统在管理内存时,每个程序运行时都会生成一个进程,每个进程都会被分配一个独立的进程地址空间
.如果只让一个进程在内存中,开始运行时全部加载,然后运行完了再释放内存,那样像硬盘一样直接通过内存的实际地址访问是没太多问题的. 但如果是多进程共用内存,并且还会在硬盘和内存间切换,此时就会有问题:假如你的程序跑了一部分,然后全部切换到硬盘,那些保存的信息里面有涉及到内存地址.等再切换到内存中时,你之前占的那内存可能被别的进程占用了,而系统分给你的是另外一块内存.此时程序如果根据之前那些内存地址信息再读写数据时就会出错.咋整呢3?
所以为了避免上述问题,进程地址空间里的地址为虚拟地址
,而不是实际的物理地址.计算机先把所有指针指定虚拟地址,比如从0开始递增,然后实际上加载到内存中时(每次加载时的起始地址
可能不一样,起始地址放在寄存器中),再用逻辑地址做偏移量加上内存中的实际起始地址就得到实际的内存地址.每个虚拟地址都从0地址
(有的编译器定义为NULL
)开始。在通常使用的保护模式
下,C/C++编程读取的变量地址值(&
或malloc()
或new()
)也正是进程在执行的时候,看到和使用的内存地址,即虚拟地址也称为逻辑地址
。C/C++通过指针变量等方式操作的内存大部分情况下都是在操作系统分配的内存段操作,是只和逻辑地址打交道的。该地址是相对于当前进程数据段的地址,不和绝对物理地址相干。
实际上也有些特殊的情况指针会指向内存物理地址,比如在嵌入式系统中,可能就一个进程用那内存,不用做啥切换,我们就可以用指针直接指向实际的物理内存地址.另外一般的桌面电脑中,内存中会划分出一块固定的内存来加载操作系统代码.操作系统中一部分内核程序会常驻内存,一直呆一个固定的地方,不会被切换到硬盘上去.此时指针也可以直接指向物理内存.3
3.5. 内部地址转换
操作系统一般通过MMU
(Memory Management Unit,内存管理单元)将进程使用的虚拟地址转换为线性地址
之后再转换为物理地址
。一个逻辑地址由一个段标识符+一个指定段内相对地址的偏移量组成,即[段标识符
:段内偏移量
]。将内存分为若干段并结合相对偏移量表示逻辑地址(或者也有物理地址?)的方式可以有效地缩减地址的表示长度,从而缓解寄存器位数小于实际寻址位数(l例如16位对20位)的矛盾4.
程序代码产生的逻辑地址作为段中偏移地址
加上相应的基地址之后生成线性地址。在32位系统中,基地址等于0,因此线性地址=段内偏移=逻辑地址。如果没有启用分页机制(比如在实模式
下),线性地址等于物理地址,否则需再经页目录
和页表
中的项变换产生物理地址,即最终的CPU外部地址总线
上的寻址物理地址内存的地址信号(再选中内存中响应的内存单元?)。内部地址转换机制对于一般程序员来说是完全透明的,仅由系统编程人员涉及5。
4. Is there anything else?
- 对于地址我们可用加减法。地址的加法主要用于向下寻址,一般用于数组等占用连续内存空间的数据结构。一般是地址加上
n
数值,表示向后偏移n
单位。而指针的偏移是向后偏移一定的元素。如对于int型的指针,每+1
,向后偏移4
个byte,short偏移2
byte;对于char型的指针,偏移1
byte。即
- 对于内存容量的提升需要有相应的硬件基础支撑,需要有能消化掉这么多内存的寻址地址。如果给一个8位单片机装载4GB的RAM,那就是暴殄天物。8位是指下面哪个呢?
-
地址总线的宽度
-
参与运算的寄存器的宽度/寄存器的
字长
(word length) -
CPU一次数据
吞吐量
/ALU位数/CPU一次处理的数据宽度/CPU同时处理数据的最大位数8位首先指参与运算的寄存器能容纳的数据长度也即寄存器的字长,同时也是ALU位数,其次这时CPU
地址总线
宽度是8位
,但也可能是16位6.当地址总线不满足数据总线的位宽,会出现硬件中断
,当数据总线不满足地址总线的位宽时分开读取. 当8位单片机地址总线为16位,此时每根高低电平
的不同有2^16
次不同情况,即可以确定2^16 次不同的线路,从而到达2^16 个存储元件
,又每个存储元件存储1位
二进制代码“0”或“1”,即1Bit大小,所以此时产生的16位进程
寻址的地址空间
为2^16 b=2^6 kb=64kb。寻址地址空间只有这么大,使用4GB内存即64*2^16kb自然是暴殄天物了。