zoukankan      html  css  js  c++  java
  • 地址、指针与引用

    计算机本身是不认识程序中给的变量名,不管我们以何种方式给变量命名,最终都会转化为相应的地址,编译器会生成一些符号常量并且与对应的地址相关联,以达到访问变量的目的。  

    变量是在内存中用来存储数据以供程序使用,变量主要有两个部分构成:变量名、变量类型,其中变量名对应了一块具体的内存地址,而变量类型则表明该如何翻译内存中存储的二级制数。我们知道不同的类型翻译为二进制的值不同,比如整型是直接通过数学转化、浮点数是采用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
    ", pnValue, *pnValue);
        printf("pfValue = %x, *pfValue = %f
    ", pfValue, *pfValue);
        printf("pcValue = %x, *pcValue = %c
    ", 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
    ", 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
    " (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“表示只操作该地址中的一个字节。

    对于地址我们可以进行加法和减法操作,地址的加法主要用于向下寻址,一般用于数组等占用连续内存空间的数据结构,一般是地址加上一个数值,表示向后偏移一定的单位,指针同样也有这样的操作,但是与地址值不同的是指针每加一个单位,表示向后偏移一个元素,而地址值加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) *n

    根据上面的加法公式我们可以推导出两个指针的减法公式,TYPE *p1, TYPE* p2: p2 - p1 = ((int)p2 - (int)p1) / sizeof(TYPE),两个指针相减得到的结果是两个指针之间拥有元素的个数。只有同类型的指针之间才可以相减。而指针的乘除法则没有意义,地址之间的乘除法也没有意义。

      引用是在C++中提出的,是变量的一个别名,提出引用主要是希望减少指针的使用,引用于指针在一个函数中想上述例子中那样使用并没有太大的意义,大量使用它们是在函数中,作为参数传递,不仅可以节省效率,同时也可以传递一段缓冲,作为输出参数来使用。这大大提升了程序的效率以及灵活性。但是在一些新手程序员看来指针无疑是噩梦般的存在,所以C++引入了引用,希望代替指针。在一般的C++书中都说引用是变量的一个别名是不占内存的,但是我通过查看反汇编代码发现引用并不是向书上说的那样,下面是一段程序及它的反汇编代码:

    int nValue = 10;
    int &rValue = nValue;
    printf("%d
    ", 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
    ", rValue);
    00401275   mov         ecx,dword ptr [ebp-8]
    00401278   mov         edx,dword ptr [ecx]
    0040127A   push        edx
    0040127B   push        offset string "%d
    " (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
    ", *pValue);
    00401275   mov         ecx,dword ptr [ebp-8]
    00401278   mov         edx,dword ptr [ecx]
    0040127A   push        edx
    0040127B   push        offset string "%d
    " (0042e01c)
    00401280   call        printf (00401520)
    两种情况的汇编代码完全一样,也就是说引用其实就是指针,编译器将其包装了一下,使它的行为变得和使用变量相同,而且在语法层面上做了一个限制,引用在定义的时候必须初始化,且初始化完成后就不能指向其他变量,这个行为与常指针相同。



  • 相关阅读:
    添加绝对路径的链接
    css-------------控制溢出隐藏 换行用省略号表示
    引入公共头部 脚部
    入口图片放在浏览器正中间,点击之后缩小固定在浏览器一侧
    伪类 统一添加样式
    nav 鼠标移入当前高亮显示,其他消失
    17/9/6 bootstrap.css去掉后引发的样式错乱
    JavaScript的常见兼容问题及相关解决方法(chrome/IE/firefox)
    javascript特效实现——当前时间和倒计时效果
    使用DataTables导出excel表格
  • 原文地址:https://www.cnblogs.com/lanuage/p/7725754.html
Copyright © 2011-2022 走看看