这可能更多地是关于计算机体系结构的问题,而不是关于 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;
}
我的猜测是第一个版本中的增量函数直接访问全局变量,没有任何指针。第二个版本在函数的堆栈帧中分配一个指向局部变量的指针并间接访问它。通过快速搜索,第三个版本显然与第二个版本完全相同(无论如何在优化之前)。来源:How is reference implemented internally?
但是理论上(或者甚至在实践中,经过一些编译器的优化),第三个函数是否可以在没有指针的情况下直接访问它自己的堆栈帧之外的局部变量?或者这种行为是全局变量独有的,因为它们在内存中的位置是静态的?
我问这个是因为我认为创建和取消引用指针应该占用少量的时间和内存。在诸如传递十几个引用的深度递归函数之类的东西中,时间和内存可能会增加。
附言我还应该特别提到内联函数,因为它们甚至不会生成新的堆栈帧。即,如果函数是 inline int increment(int*)
和 inline int increment(int&)
,版本 2 和 3 的汇编代码是否会有所不同,或者编译器只是优化在那种情况下离开指针?
最佳答案
由于不同的编译器会以不同的方式处理此问题,因此我将向您展示 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++ 中的设计略有不同。
由于评论中的一些问题而更新:
does using a global variable produce different assembly output
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 中,递增并恢复到旧的内存位置。
is it fundamentally impossible to implement references in the same way as global variables (i.e. accessing them without pointers)
2) 我不明白你的问题。您可以拥有全局引用,根据规则必须直接指向某些东西:例如另一个初始化变量,并且除了持有像 50 这样的值外,其行为相同,它将持有另一个全局变量的地址。
i.e. accessing them without pointers
这部分让我感到困惑,在谈论引用时没有意义。您不会通过指针访问引用,这在 C++ 中是不可能的。您甚至无法获得引用地址。
关于c++ - 可以在没有指针的情况下实现引用传递吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31643828/