This might be more a question on computer architecture than on C++ itself, but I was wondering if pass-by-reference could theoretically be implemented without using pointers in a language like C++.
Here are three examples of code that have similar function and structure.
//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;
}
My guess is that the increment function in the first version accesses the global variable directly, without any pointers. The second version allocates a pointer in the function's stack frame pointing to the local variable and access it indirectly. From a quick search, the third version is apparently implemented (before optimization anyway) exactly like the second version. Source: How is reference implemented internally?
But in theory (or even in practice, after optimization by some compiler), could the third function directly access the local variable outside its own stack frame without a pointer? Or is that behavior exclusive to global variables, since their locations in memory are static?
I ask this because I would think that creating and dereferencing pointers should take up a small amount of time and memory. In something such as a deep recursive function that passes around a dozen references, that time and memory could add up.
P.S. I should also specifically mention inline functions, seeing as they don't even generate new stack frames. i.e. Would the assembly code for versions 2 and 3 vary if the functions were inline int increment(int*)
and inline int increment(int&)
, or would the compiler just optimize away the pointer in that case?
Since this can be handled differently by different compilers I'll show you a example of how msvc++ handles the following code:
#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;
}
As you can see, both versions of Increament() produces the exact same code, it loads the effective address of x into register eax and pushes the address on to the stack.
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
As for the rest of your questions, the compiler is free to do whatever it likes and the result will differ from one another.
The reason i posted this is to make you understand that there is no reference in asm and references are treated as pointers as far as the compiler is concerned and in fact, references are pointers with restrictions with a slightly different design in c++.
UPDATE due to some questions in the comment:
does using a global variable produce different assembly output
1) The first example in your code will produce different assembly because global_var is global and being initialized which will store it in the data segment instead.
Lets take a look:
#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;
}
Which produces the following assembly for the function, notice that nothing is being pushed here:
00A61000 mov eax,dword ptr ds:[00A63018h]
00A61005 inc eax
00A61006 mov dword ptr ds:[00A63018h],eax
0x00A63018 is the address of global_var in memory and its value is being stored in eax, incremented and restored back to the old memory location.
is it fundamentally impossible to implement references in the same way as global variables (i.e. accessing them without pointers)
2) I don't understand your question. You can have global references which by rule must directly point to something: another initialized variable for example and will act the same way except for holding a value like 50, it will hold the address to the other global variable.
i.e. accessing them without pointers
This part is what confuses me and that doesn't make sense when talking about references. You dont access references by pointers thats impossible in c++. You cant even get the address of a reference.