代码之家  ›  专栏  ›  技术社区  ›  Keynslug

如何在内部实施样板客户?

  •  24
  • Keynslug  · 技术社区  · 14 年前

    只是想知道它是如何在不同的编译器和调试/发布配置之间实现的。标准是否以某种方式提供了实施建议?有什么不同吗?

    我试着运行一个简单的程序,在那里我从函数返回了非常量引用和指向局部变量的指针,但是它的运行方式是一样的。那么,内部引用是否只是一个指针呢?

    7 回复  |  直到 8 年前
        1
  •  13
  •   Mark Wilkins    12 年前

    引用的自然实现实际上是一个指针。但是,不要在代码中依赖于这一点。

        2
  •  38
  •   SingleNegationElimination    14 年前

    为了重复大家所说的一些内容,让我们来看一些编译器输出:

    #include <stdio.h>
    #include <stdlib.h>
    
    int byref(int & foo)
    {
      printf("%d\n", foo);
    }
    int byptr(int * foo)
    {
      printf("%d\n", *foo);
    }
    
    int main(int argc, char **argv) {
      int aFoo = 5; 
      byref(aFoo);
      byptr(&aFoo);
    }
    

    我们可以使用llvm(在关闭优化的情况下)编译此文件,得到以下信息:

    define i32 @_Z5byrefRi(i32* %foo) {
    entry:
      %foo_addr = alloca i32*                         ; <i32**> [#uses=2]
      %retval = alloca i32                            ; <i32*> [#uses=1]
      %"alloca point" = bitcast i32 0 to i32          ; <i32> [#uses=0]
      store i32* %foo, i32** %foo_addr
      %0 = load i32** %foo_addr, align 8              ; <i32*> [#uses=1]
      %1 = load i32* %0, align 4                      ; <i32> [#uses=1]
      %2 = call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %1) ; <i32> [#uses=0]
      br label %return
    
    return:                                           ; preds = %entry
      %retval1 = load i32* %retval                    ; <i32> [#uses=1]
      ret i32 %retval1
    }
    
    define i32 @_Z5byptrPi(i32* %foo) {
    entry:
      %foo_addr = alloca i32*                         ; <i32**> [#uses=2]
      %retval = alloca i32                            ; <i32*> [#uses=1]
      %"alloca point" = bitcast i32 0 to i32          ; <i32> [#uses=0]
      store i32* %foo, i32** %foo_addr
      %0 = load i32** %foo_addr, align 8              ; <i32*> [#uses=1]
      %1 = load i32* %0, align 4                      ; <i32> [#uses=1]
      %2 = call i32 (i8*, ...)* @printf(i8* noalias getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %1) ; <i32> [#uses=0]
      br label %return
    
    return:                                           ; preds = %entry
      %retval1 = load i32* %retval                    ; <i32> [#uses=1]
      ret i32 %retval1
    }
    

    两种功能的主体是相同的

        3
  •  13
  •   Prasad Rane    8 年前

    很抱歉使用assembly解释这一点,但我认为这是理解编译器如何实现引用的最佳方法。

        #include <iostream>
    
        using namespace std;
    
        int main()
        {
            int i = 10;
            int *ptrToI = &i;
            int &refToI = i;
    
            cout << "i = " << i << "\n";
            cout << "&i = " << &i << "\n";
    
            cout << "ptrToI = " << ptrToI << "\n";
            cout << "*ptrToI = " << *ptrToI << "\n";
            cout << "&ptrToI = " << &ptrToI << "\n";
    
            cout << "refToNum = " << refToI << "\n";
            //cout << "*refToNum = " << *refToI << "\n";
            cout << "&refToNum = " << &refToI << "\n";
    
            return 0;
        }
    

    此代码的输出如下

        i = 10
        &i = 0xbf9e52f8
        ptrToI = 0xbf9e52f8
        *ptrToI = 10
        &ptrToI = 0xbf9e52f4
        refToNum = 10
        &refToNum = 0xbf9e52f8
    

    让我们来看看反汇编(我用了gdb实现这个目的)。8、9和10这是代码的行号)

    8           int i = 10;
    0x08048698 <main()+18>: movl   $0xa,-0x10(%ebp)
    

    在这里 $0xa 是我们分配给的10(十进制) i . -0x10(%ebp) 这里的意思是 ebp register 16(十进制)。 -0x10(%EBP) 指向的地址 堆栈上。

    9           int *ptrToI = &i;
    0x0804869f <main()+25>: lea    -0x10(%ebp),%eax
    0x080486a2 <main()+28>: mov    %eax,-0x14(%ebp)
    

    分配地址 ptrToI . 帕特罗伊 -0x14(%ebp) ,那就是 ebp 20(十进制)。

    10          int &refToI = i;
    0x080486a5 <main()+31>: lea    -0x10(%ebp),%eax
    0x080486a8 <main()+34>: mov    %eax,-0xc(%ebp)
    

    现在,抓住了!比较9号线和10号线的拆卸情况,你会发现, -0x14(%EBP) 被替换 -0xc(%ebp) 在第10行。 -0xC(%EBP) 是的地址 refToNum . 它在堆栈上分配。但是你永远无法从代码中获得这个地址,因为你不需要知道地址。

    所以,引用确实占用了内存。在本例中,它是堆栈内存,因为我们已经将其分配为局部变量。 它占用了多少内存? 指针所占的空间。

    现在让我们看看如何访问引用和指针。为了简单起见,我只显示了程序集片段的一部分

    16          cout << "*ptrToI = " << *ptrToI << "\n";
    0x08048746 <main()+192>:        mov    -0x14(%ebp),%eax
    0x08048749 <main()+195>:        mov    (%eax),%ebx
    19          cout << "refToNum = " << refToI << "\n";
    0x080487b0 <main()+298>:        mov    -0xc(%ebp),%eax
    0x080487b3 <main()+301>:        mov    (%eax),%ebx
    

    现在,将上面的两行进行比较,您将看到显著的相似性。 -0xC(%EBP) 是的实际地址 refToI 你永远无法接近。 简单来说,如果将引用视为普通指针,那么访问引用就像在引用指向的地址处获取值。这意味着下面两行代码将给出相同的结果

    cout << "Value if i = " << *ptrToI << "\n";
    cout << " Value if i = " << refToI << "\n";
    

    现在比较一下这个

    15          cout << "ptrToI = " << ptrToI << "\n";
    0x08048713 <main()+141>:        mov    -0x14(%ebp),%ebx
    21          cout << "&refToNum = " << &refToI << "\n";
    0x080487fb <main()+373>:        mov    -0xc(%ebp),%eax
    

    我想你能发现这里发生了什么。 如果你要求 &refToI ,的内容 -0xC(%EBP) 地址位置已返回,并且 -0xC(%EBP) 是哪里 refToi 居住及其内容只是地址 .

    最后一件事,为什么这行被评论了?

    //cout << "*refToNum = " << *refToI << "\n";
    

    因为 *refToI 是不允许的,它会给您一个编译时错误。

        4
  •  4
  •   Saurav Sahu    8 年前

    用比亚恩的话说:

    就像一个指针,一个 参考 是对象的别名,通常实现为 保留机器地址 对象的,并且与指针相比不强制执行性能开销,但它与指针的区别在于:

    您可以使用与对象名称完全相同的语法访问引用。

    引用始终引用初始化它的对象。

    没有空引用,我们可以假定引用引用引用了一个对象


    虽然一 参考 事实上是 指针 但不能像 指针 但作为一个 别名 .

        5
  •  2
  •   kamerunka    8 年前

    指针不需要引用。 在许多情况下,它是,但在其他情况下,它只是一个别名,不需要为指针单独分配内存。 程序集示例并不总是正确的,因为它们严重依赖于优化以及编译器的“智能”程度。

    例如: INTI; 国际标准化组织;

    不需要生成任何附加代码或分配任何附加内存。

        6
  •  1
  •   Mike    14 年前

    我不能肯定这是对的,但我在谷歌上搜索了一下,发现了以下声明:

    语言标准不要求 任何特定的机制。各 实现可以在任何 方式,只要行为是 顺从的

    来源: Bytes.com

        7
  •  1
  •   user2615724    11 年前

    引用不是指针。这是事实。指针可以绑定到另一个对象,有自己的操作,如取消引用和递增/递减。

    尽管在内部,引用可以作为指针实现。但这是一个实现细节,它不会改变引用不能与指针交换的事实。如果引用是作为指针实现的,则不能编写代码。