0

这可能是关于计算机体系结构的问题,而不是 C++ 本身的问题,但我想知道在理论上是否可以在不使用 C++ 之类的语言中的指针的情况下实现传递引用。

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

//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;
}

我的猜测是第一个版本中的增量函数直接访问全局变量,没有任何指针。第二个版本在函数的堆栈帧中分配一个指向局部变量的指针并间接访问它。从快速搜索来看,第三版显然与第二版完全一样(在优化之前)。资料来源:参考是如何在内部实现的?

但是理论上甚至在实践中,经过一些编译器的优化),第三个函数可以直接访问它自己的堆栈框架之外的局部变量而不需要指针吗?或者这种行为是全局变量独有的,因为它们在内存中的位置是静态的?

我问这个是因为我认为创建和取消引用指针应该占用少量的时间和内存。在诸如传递十几个引用的深度递归函数之类的东西中,时间和内存可能会加起来。

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

4

2 回答 2

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++ 中不可能的指针访问引用。您甚至无法获得参考地址。

于 2015-07-27T02:11:01.690 回答
0

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

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

于 2015-07-28T02:02:53.260 回答