计算机自己是不认识程序中给的变量名,无论咱们以何种方式给变量命名,最终都会转化为相应的地址,编译器会生成一些符号常量而且与对应的地址相关联,以达到访问变量的目的。程序员
变量是在内存中用来存储数据以供程序使用,变量主要有两个部分构成:变量名、变量类型,其中变量名对应了一块具体的内存地址,而变量类型则代表该如何翻译内存中存储的二级制数。咱们知道不一样的类型翻译为二进制的值不一样,好比整型是直接经过数学转化、浮点数是采用IEEE的方法、字符则根据ASCII码转化,一样变量类型决定了变量所占的内存大小,以及如何在二进制和变量所表达的真正意义之间转化。而指针变量也是一个变量,在内存中也占空间,不过比较特殊的是它存储的是其余变量的地址。在32位的机器中,每一个进程能访问4GB的内存地址空间,因此程序中的地址采用32位二进制数表示,也就是一个整型变量的长度,地址值通常没有负数因此准确的说指针变量的类型应该是unsigned int 即每一个指针变量占4个字节。还记得在定义结构体中可使用该结构体的指针做为成员,可是不能使用该结构的实例做为成员吗?这是由于编译器须要根据各个成员变量的大小分配相关的内存,用该结构体的实例做为成员时,该结构体根本没有定义完整,编译器是不会知道该如何分配内存的,而任何类型的指针都只占4个字节,编译器天然知道如何分配内存。咱们在书写指针变量时给定的类型是它所指向的变量的类型,这个类型决定了如何翻译所对应内存中的值,以及该访问多少个字节的内存。对指针的间接访问会先先取出值,访问到对应的内存,再根据指针所指向的变量的类型,翻译成对应的值。通常指针只能指向对应类型的变量,好比int类型的指针只能指向int型的变量,而有一种指针变量能够指向全部类型的变量,它就是void类型的指针变量,可是因为这种类型的变量没有指定它所对应的变量的类型,因此即便有了对应的地址,它也不知道该取多大内存的数据,以及如何解释这些数据,因此这种类型的指针不支持间接访问,下面是一个间接访问的例子:数组
int main() { int nValue = 10; float fValue = 10.0f; char cValue = 'C'; int *pnValue = &nValue; float *pfValue = &fValue; char *pcValue = &cValue; printf("pnValue = %x, *pnValue = %d\n", pnValue, *pnValue); printf("pfValue = %x, *pfValue = %f\n", pfValue, *pfValue); printf("pcValue = %x, *pcValue = %c\n", pcValue, *pcValue); return 0; }
下面是它对应的反汇编代码(部分):数据结构
10: int nValue = 10; 00401268 mov dword ptr [ebp-4],0Ah 11: float fValue = 10.0f; 0040126F mov dword ptr [ebp-8],41200000h 12: char cValue = 'C'; 00401276 mov byte ptr [ebp-0Ch],43h 13: int *pnValue = &nValue; 0040127A lea eax,[ebp-4] 0040127D mov dword ptr [ebp-10h],eax 14: float *pfValue = &fValue; 00401280 lea ecx,[ebp-8] 00401283 mov dword ptr [ebp-14h],ecx 15: char *pcValue = &cValue; 00401286 lea edx,[ebp-0Ch] 00401289 mov dword ptr [ebp-18h],edx 16: printf("pnValue = %x, *pnValue = %d\n", pnValue, *pnValue); 0040128C mov eax,dword ptr [ebp-10h] 0040128F mov ecx,dword ptr [eax] 00401291 push ecx 00401292 mov edx,dword ptr [ebp-10h] 00401295 push edx 00401296 push offset string "pnValue = %x, *pnValue = %d\n" (00432064) 0040129B call printf (00401580) 004012A0 add esp,0Ch
从上面的汇编代码能够看到指针变量会占内存空间,它们的地址分别是:[ebp - 10h] 、 [ebp - 14h]、 [ebp - 18h],在给指针变量赋值时首先将变量的地址赋值给临时寄存器,而后将寄存器的值赋值给指针变量,而经过间接访问时也通过了一个临时寄存器,先将指针变量的值赋值给临时寄存器(mov eax,dword ptr [ebp-10h])而后经过这个临时寄存器访问变量的地址空间,获得变量值( mov ecx,dword ptr [eax]),因为间接访问进过了这几步,因此在效率上是比不上直接使用变量。下面是对char型变量的间接访问:函数
004012BF mov edx,dword ptr [ebp-18h] 004012C2 movsx eax,byte ptr [edx] 004012C5 push eax
首先也是将指针变量的值取出来,放到寄存器中,而后根据寄存器寻址找到变量对应的地址,访问变量。其中”bye ptr“表示只操做该地址中的一个字节。spa
对于地址咱们能够进行加法和减法操做,地址的加法主要用于向下寻址,通常用于数组等占用连续内存空间的数据结构,通常是地址加上一个数值,表示向后偏移必定的单位,指针一样也有这样的操做,可是与地址值不一样的是指针每加一个单位,表示向后偏移一个元素,而地址值加1则就是在原来的基础上加上一。指针偏移是根据其所指向的变量类型来决定的,好比有下面的程序:翻译
int main(int argc, char* argv[]) { char szBuf[5] = {0x01, 0x23, 0x45, 0x67, 0x89}; int *pInt = (int*)szBuf; short *pShort = (short*)szBuf; char *pChar = szBuf; pInt += 1; pShort += 1; pChar += 1; return 0; }
它的汇编代码以下:指针
9: char szBuf[5] = {0x01, 0x23, 0x45, 0x67, 0x89}; 00401028 mov byte ptr [ebp-8],1 0040102C mov byte ptr [ebp-7],23h 00401030 mov byte ptr [ebp-6],45h 00401034 mov byte ptr [ebp-5],67h 00401038 mov byte ptr [ebp-4],89h 10: int *pInt = (int*)szBuf; 0040103C lea eax,[ebp-8] 0040103F mov dword ptr [ebp-0Ch],eax 11: short *pShort = (short*)szBuf; 00401042 lea ecx,[ebp-8] 00401045 mov dword ptr [ebp-10h],ecx 12: char *pChar = szBuf; 00401048 lea edx,[ebp-8] 0040104B mov dword ptr [ebp-14h],edx 13: 14: pInt += 1; 0040104E mov eax,dword ptr [ebp-0Ch] 00401051 add eax,4 00401054 mov dword ptr [ebp-0Ch],eax 15: pShort += 1; 00401057 mov ecx,dword ptr [ebp-10h] 0040105A add ecx,2 0040105D mov dword ptr [ebp-10h],ecx 16: pChar += 1; 00401060 mov edx,dword ptr [ebp-14h] 00401063 add edx,1 00401066 mov dword ptr [ebp-14h],edx
根据其汇编代码能够看出,对于int型的指针,每加1个会向后偏移4个字节,short会偏移2个字节,char型的会偏移1个,因此根据以上的内容,能够得出一个公式:TYPE* P p + n = p + sizeof(TYPE) *ncode
根据上面的加法公式咱们能够推导出两个指针的减法公式,TYPE *p1, TYPE* p2: p2 - p1 = ((int)p2 - (int)p1) / sizeof(TYPE),两个指针相减获得的结果是两个指针之间拥有元素的个数。只有同类型的指针之间才能够相减。而指针的乘除法则没有意义,地址之间的乘除法也没有意义。blog
引用是在C++中提出的,是变量的一个别名,提出引用主要是但愿减小指针的使用,引用于指针在一个函数中想上述例子中那样使用并无太大的意义,大量使用它们是在函数中,做为参数传递,不只能够节省效率,同时也能够传递一段缓冲,做为输出参数来使用。这大大提高了程序的效率以及灵活性。可是在一些新手程序员看来指针无疑是噩梦般的存在,因此C++引入了引用,但愿代替指针。在通常的C++书中都说引用是变量的一个别名是不占内存的,可是我经过查看反汇编代码发现引用并非向书上说的那样,下面是一段程序及它的反汇编代码:进程
int nValue = 10; int &rValue = nValue; printf("%d\n", rValue);
10: int nValue = 10; 00401268 mov dword ptr [ebp-4],0Ah 11: int &rValue = nValue; 0040126F lea eax,[ebp-4] 00401272 mov dword ptr [ebp-8],eax 12: printf("%d\n", rValue); 00401275 mov ecx,dword ptr [ebp-8] 00401278 mov edx,dword ptr [ecx] 0040127A push edx 0040127B push offset string "%d\n" (0042e01c) 00401280 call printf (00401520)
从汇编代码中能够看到,在定义引用并为它赋值的过程当中,编译器实际上是将变量的地址赋值给了一个新的变量,这个变量的地址是[ebp - 8h],在调用printf函数的时候,编译器将地址取出并将它压到函数栈中。下面是将引用改成指针的状况:
10: int nValue = 10; 00401268 mov dword ptr [ebp-4],0Ah 11: int *pValue = &nValue; 0040126F lea eax,[ebp-4] 00401272 mov dword ptr [ebp-8],eax 12: printf("%d\n", *pValue); 00401275 mov ecx,dword ptr [ebp-8] 00401278 mov edx,dword ptr [ecx] 0040127A push edx 0040127B push offset string "%d\n" (0042e01c) 00401280 call printf (00401520)
两种状况的汇编代码彻底同样,也就是说引用其实就是指针,编译器将其包装了一下,使它的行为变得和使用变量相同,并且在语法层面上作了一个限制,引用在定义的时候必须初始化,且初始化完成后就不能指向其余变量,这个行为与常指针相同。