but I’m still trusting that author. I was curious if I could prove it, or disapprove it on any system.
I understand and admire this. Don't trust me. Granted, at some point you're going to have to trust someone. I'm going to assume that you trust that a stack allocated variable will be referenced by way of the stack pointer. On x86, that's a register called "RSP" and I'll show you exactly how you can see for yourself what gcc/g++ is outputting.
Here's some code: test.cpp
#include <iostream>
using namespace std;
int main(void) {
int x;
x = 42;
cout << "x = " << x;
return 0;
}
You can have g++ convert this to assembly with comments with: g++ -S -fverbose-asm test.s test.cpp
Here's a portion of the output on my system:
main:
.LFB1988:
.cfi_startproc
endbr64
pushq %rbp #
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp #,
.cfi_def_cfa_register 6
subq $16, %rsp #,
# test.cpp:4: int x = 42;
movl $42, -4(%rbp) #, x
# test.cpp:5: std::cout << "x = 42";
The piece I'll point to is the references to the x variable:
# test.cpp:4: int x = 42;
movl $42, -4(%rbp) #, x
This is moving the constant 42 ($42) in to the memory location -4(%rbp). This is a way of basically saying "the value stored in %rbp minus 4". I did say %rsp is the stack pointer, so why is it referencing %rbp? Well, see a couple lines above: movq %rsp, %rbp #, (it's copying the value of %rsp in to %rbp and then referencing via %rbp, so %rbp is identical to what %rsp was before the extra variable allocations at the time of the assignment of x = 42). Also note in later examples how the offset (-4 in this example) to %rbp (and %rsp) changes as we add more variables.
OK so how can we actually see the value of %rsp (or %rbp)? Well with some inline assembler to copy the value of %rsp in to a variable and then print it. So here's an example printing x, pStack, and their addresses:
#include <iostream>
using namespace std;
int main(void) {
int x;
x = 42;
int *pStack;
cout << "x = " << x << endl;
cout << "&x = " << &x << endl;
__asm__("movq %%rsp, %0;" : "=m" (pStack));
cout << "pStack = " << pStack << endl;
cout << "&pStack = " << &pStack << endl;
return 0;
}
On my system, here's the output I get when I run it:
$ ./test2
x = 42
&x = 0x7ffcbd78a0bc
pStack = 0x7ffcbd78a0b0
&pStack = 0x7ffcbd78a0c0
So you can see pStack is address 0x7ffcbd78a0b0 and x is address 0x7ffcbd78a0bc. Try this with more complex types (struct, class) and see that the variable addresses are lower than the stack pointer.
Now for a heap allocation:
test3.cpp
#include <iostream>
using namespace std;
int main(void) {
int *x = new int;
*x = 42;
int *pStack;
cout << "x = " << x << endl;
cout << "&x = " << &x << endl;
__asm__("movq %%rsp, %0;" : "=m" (pStack));
cout << "pStack = " << pStack << endl;
cout << "&pStack = " << &pStack << endl;
return 0;
}
When I run it, I get:
$ ./test3
x = 0x5ca98bc312b0
&x = 0x7ffd768137c8
pStack = 0x7ffd768137c0
&pStack = 0x7ffd768137d0
So we can see that x (a pointer) is pointing to a drastically different memory address (it's the heap). So why is &x (address of x) the stack? Well because x itself is a stack variable, but it's a pointer that is pointing to heap allocated memory.
You can do this kind of test with your custom classes, fixed sized arrays like int x[10] or even with arrays of complex types BetterDouble bd[10] and so on until you're convinced.
My only way of verifying this that I can find is verifying copy. Basically make a variable and set it to something, make second variable and set it equal to first, modify second variable, test if variable 1 == variable 2. If yes, it’s heap allocated, if not it’s stack allocated. My understanding is no matter what you create, a value is created on the stack. The difference is if stack allocated the data is stored directly, if heap the data on the stack is a pointer to an address on the heap. As such if you copy the variable, your copying the value in the stack. If it’s stack allocated the data is literally copied and you have the same data in two places, when you modify one the other is left alone. If it’s heap allocated, the address is copied into a new spot on the stack, but since both point to same address if you modify one you modify both.
This may just be a terminology difference or I'm misreading, but I think you may be confusing pointers vs the data they point to and stack vs heap. The pointers and stack/heap are 2 separate, although somewhat related, concepts. What I mean is that the behavior you describe (modifying variable2 and having it impact variable1) is true if variable2 is a pointer to variable1 and you're modifying the data contained (what variable2 is pointing to), but that is independent of stack vs heap. Generally you reference heap allocated variables via pointers, but you can also reference stack allocated variables by pointer. Here's an example using only stack variables (and pointers).
#include <iostream>
using namespace std;
int main(void) {
int x = 42;
int *y = &x;
int *pStack;
__asm__("movq %%rsp, %0;" : "=m" (pStack));
cout << "pStack = " << pStack << endl;
cout << "&pStack = " << &pStack << endl;
cout << "x = " << x << endl;
cout << "&x = " << &x << endl;
cout << "y = " << y << endl;
cout << "*y = " << *y << endl;
(*y)++;
cout << "x = " << x << endl;
cout << "&x = " << &x << endl;
cout << "y = " << y << endl;
cout << "*y = " << *y << endl;
return 0;
}
Output:
$ ./test4
pStack = 0x7ffd87bbb440
&pStack = 0x7ffd87bbb448
x = 42
&x = 0x7ffd87bbb444
y = 0x7ffd87bbb444
*y = 42
x = 43
&x = 0x7ffd87bbb444
y = 0x7ffd87bbb444
*y = 43
Key points:
- address of both x and y are stack
- y is pointing to the address of x
- updating the data pointed to by y causes x to be updated (because y is pointing to the address of x)
- there's no heap references here
Another concept that is related to this is "pass by reference" vs "pass by copy", so you may want to read about that.
EDIT: fixed a typo (higher/lower with respect to stack memory address). I'll blame my rambling on being unemployed.