可以在没有指针的情况下实现引用传递吗?
Could Pass-by-Reference be implemented without pointers?
这可能更多地是关于计算机体系结构的问题,而不是关于 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?
但是理论上(甚至在实践中,经过一些编译器的优化),第三个函数是否可以直接访问局部变量在没有指针的情况下在自己的堆栈框架之外?或者这种行为是全局变量独有的,因为它们在内存中的位置是静态的?
我问这个是因为我认为创建和取消引用指针应该占用 小 的时间和内存量。在诸如传递十几个引用的深度递归函数之类的东西中,时间和内存可能会增加。
P.S。我还应该特别提到内联函数,因为它们甚至不会生成新的堆栈帧。即,如果函数是 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 代码实现。
大多数汇编语言通过地址访问一切(即使它是编译完全不使用指针或引用的 C 代码的结果),除了那些短暂的某物在寄存器中的时间。
这可能更多地是关于计算机体系结构的问题,而不是关于 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?
但是理论上(甚至在实践中,经过一些编译器的优化),第三个函数是否可以直接访问局部变量在没有指针的情况下在自己的堆栈框架之外?或者这种行为是全局变量独有的,因为它们在内存中的位置是静态的?
我问这个是因为我认为创建和取消引用指针应该占用 小 的时间和内存量。在诸如传递十几个引用的深度递归函数之类的东西中,时间和内存可能会增加。
P.S。我还应该特别提到内联函数,因为它们甚至不会生成新的堆栈帧。即,如果函数是 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 代码实现。
大多数汇编语言通过地址访问一切(即使它是编译完全不使用指针或引用的 C 代码的结果),除了那些短暂的某物在寄存器中的时间。