可以在没有指针的情况下实现Pass-by-Reference吗?

时间:2015-07-27 01:40:50

标签: c++ pass-by-reference pass-by-pointer

这可能是计算机体系结构上的一个问题,而不是C ++本身,但我想知道理论上是否可以在不使用C ++等语言的指针的情况下实现pass-by-reference。

以下是三个具有相似功能和结构的代码示例。

//Version 1: Uses global variable

#include <iostream>

int global_var = 0;

int increment(void)      {return ++global_var;}

int main(void)
{
    while (global_var < 100)
        printf("Counter: %i\n", increment());
    return 0;
}
//Version 2: Uses pass-by-pointer

#include <iostream>

int increment(int* ptr)   {return ++(*ptr);}

int main(void)
{
    int local_var = 0;
    while (local_var < 100)
        printf("Counter: %i\n", increment(&local_var));
    return 0;
}
//Version 3: Uses pass-by-reference

#include <iostream>

int increment(int& ref)   {return ++ref;}

int main(void)
{
    int local_var = 0;
    while (local_var < 100)
        printf("Counter: %i\n", increment(local_var));
    return 0;
}

我的猜测是第一个版本中的递增函数直接访问全局变量,没有任何指针。第二个版本在函数的堆栈帧中分配指向局部变量的指针并间接访问它。从快速搜索开始,第三个版本显然已经实现(无论如何优化之前),与第二个版本完全相同。资料来源:How is reference implemented internally?

理论上甚至在实践中,经过某些编译器优化后),第三个函数可以在没有指针的情况下直接访问自己的堆栈帧外的局部变量?或者这个行为是全局变量专有的,因为它们在内存中的位置是静态的吗?

我问这个是因为我认为创建和解除引用指针应占用的时间和内存。在诸如深度递归函数之类的东西中,传递了大约十二个引用,这个时间和内存可能会相加。

P.S。我还应该特别提到内联函数,因为它们甚至不会生成新的堆栈帧。即,如果函数是inline int increment(int*)inline int increment(int&),版本2和版本3的汇编代码是否会有所不同,或者编译器是否会在这种情况下优化指针?

2 个答案:

答案 0 :(得分:1)

由于不同的编译器可以对此进行不同的处理,因此我将向您展示msvc ++如何处理以下代码的示例:

#include <iostream>

__declspec(noinline) int Increament(int* p)
{
    std::cout << "Increament Pointer called" << std::endl;
    ++*p;
    return *p;
}

__declspec(noinline) int Increament(int& p)
{
    std::cout << "Increament Reference called" << std::endl;
    ++p;
    return p;
}

int main()
{
    int x = 10;
    Increament(x);
    Increament(&x);


    std::cin.get();
    return 0;
}

正如您所看到的,两个版本的Increament()生成完全相同的代码,它将x的有效地址加载到寄存器eax中并将地址压入堆栈。

int x = 10;
00A52598  mov         dword ptr [x],0Ah  
    Increament(x);
00A5259F  lea         eax,[x]  
    Increament(x);
00A525A2  push        eax  
00A525A3  call        Increament (0A5135Ch)  
00A525A8  add         esp,4  
    Increament(&x);
00A525AB  lea         eax,[x]  
00A525AE  push        eax  
00A525AF  call        Increament (0A51357h)  
00A525B4  add         esp,4  

至于其余的问题,编译器可以自由地做任何喜欢的事情,结果会有所不同。

我发布这个的原因是为了让你明白asm中没有引用,并且就编译器而言,引用被视为指针,实际上,引用是指限制,在c ++中设计略有不同。

由于评论中的一些问题而更新:

  

使用全局变量产生不同的汇编输出

1)代码中的第一个示例将生成不同的程序集,因为global_var是全局的并且正在初始化,而是将其存储在数据段中。

让我们来看看:

#include <iostream>

int global_var = 50;

__declspec(noinline) int increment(void)
{ 
    ++global_var;
    return global_var;
}

int main(void)
{
    increment();

    std::cin.get();
    return 0;
}

为该函数生成以下程序集,请注意此处没有任何内容:

00A61000  mov         eax,dword ptr ds:[00A63018h]  
00A61005  inc         eax  
00A61006  mov         dword ptr ds:[00A63018h],eax 

0x00A63018是内存中global_var的地址,其值存储在eax中,递增并恢复到旧的内存位置。

  

从根本上不可能以相同的方式实现引用   作为全局变量(即无指针访问它们)

2)我不明白你的问题。您可以使用规则必须直接指向某些内容的全局引用:例如,另一个初始化变量将以相同的方式运行,除了保持50之类的值,它将把地址保存到另一个全局变量。

  

即。无指针访问它们

这部分让我感到困惑,在谈论引用时没有意义。你不能通过c ++中不可能的指针来访问引用。你甚至无法获得参考的地址。

答案 1 :(得分:0)

与其他所有内容一样,引用通常在汇编语言内部实现,而不是作为C代码实现。

大多数汇编语言通过地址访问所有(即使它是编译完全没有使用指针或引用的C代码的结果),除了那些短暂的时期时间是什么东西在寄存器中。