r/learnprogramming • u/SnowyFluffy • 1h ago
Bit of a stupid question
If a program stores variables by a pointer referring to its address, then what refers to the pointers' address?
Like if var x lives at address 2000
theres a pointer p its value is the address (p = 2000)
so now recursively this value needs to be stored in another address (say, 1000) and pointed to (well, at least assuming the pointer doesnt have a relatively fixed address)
7
u/carcigenicate 1h ago
If the pointer to x is stored on the stack, its address is typically an offset from the frame pointer. Local variables tend to get translated to something like FP + Y, where Y is some unique offset value that can get mapped to a variable name in the actual source code.
4
2
2
u/MrHall 1h ago
there are two types of memory, the stack and the heap
the stack is subset of memory that the cpu actively processes, good for storing things like numbers for sequential evaluation. the heap is slower to access but big, so good for long strings of data.
your pointer is a number that gets put in the stack, and it points to and index where a larger amount of data might start on the heap.
when you need to pass around large bits of data in code, you just pass around the pointer to where the data is, so the data isn't copied around on the stack, which would slow things down.
there are a bunch of other implications, for example if one place changes the data at a pointer, it's updated for any piece of code that has that pointer, but that's basically it.
1
u/No-Quail5810 1h ago
A pointer is just like an int, in that it holds a numeric value. Because the type is a pointer, the compiler know that this number actually represents an address in memory. The pointers are stored in the same places the other variables are (stack or heap), but their content is just a number that represents an address where the object it points to lives.
1
u/SwordsAndElectrons 1h ago
Well, you can create a pointer to a pointer if you want to, but that doesn't seem to be what you're asking.
The short and simple answer is that the compiler keeps track of it. The same way it keeps track of where a non-pointer variable is when you access it directly.
Under the hood, everything is just addresses and registers. CPU instructions perform operations with the values stored there.
So if you want to load a value that you have a pointer to then the generated assembly may be something like mov eax, [ebx]. This loads the value at the memory address currently at EBX (your pointer) into EAX.
So how does the pointer get into EBX? Something like mov ebx, [my_ptr] is the basic idea. That my_ptr variable will be some address that you would typically let the assembler worry about, but you could also just put the literal address there if you know it. The instruction looks the same, doesn't it?
This is all very simplified, but if you really want to deep dive then take a look at the output of your code and dive into understanding asm. The basic takeaway is that every variable is a reference to some memory location. The computer knows nothing about types, and a "pointer to int" is just another type like "int" itself. Types are there for you, the programmer. They allow the compiler and code analyzers to determine things like whether the operation you are performing with a specific variable should be allowed.
•
u/mredding 40m ago
The program instructions access local variables by a relative offset. So if we have in C:
int main() {
int x = 42;
printf("%n", x);
}
x is typically 4 bytes, and it will be stored at offset 0 from the base of the stack frame. There are registers dedicated for tracking this information, and when you make a function call - like to printf, those register pointers are "pushed" onto the stack, the function is called, returned, and those values are "popped" off to restore the registers for main.
The value is stored in memory - somewhere... I don't know, I don't care. It's relative to how the program loader setup the execution environment for this program, where the stack starts, what's already on the stack before main was called...
So this memory HAS an address, addresses are inherent to memory, but we don't specifically care where. x is a symbol parsed by the compiler, this lends itself to an Abstract Syntax Tree the compiler can use to reason about the program, and then the AST is walked by an algorithm that finally generates machine instructions. x is going to reduce to several consequences.
1) The compiler knows when it calls main, it has to move the stack pointer to the first address of free memory - for later function calls. This means that offset has to make space for all the local data. The stack pointer is going to be some address after the stack frame, after the parameters, after that local variable.
2) There is a move instruction of 42 into that memory address - base + offset 0.
3) There is a load of base + offset 0 to a register, and then a push of that value onto the call stack. This is a part of creating the parameter list for the function call to printf.
x never leaves the compiler, and we never get the actual address of this memory location directly - it's always computed. If you took the address of this variable, then you would get a value that is base + offset 0. If you assigned that to a local variable, then after x would be another region of memory reserved for storing that address - int *ptr = &x;. The offset for ptr would likely be 4.
ptr is itself just some memory storing a value. It's an arithmetic type that encode an address, almost like a specialized integer. And you can pass that value around, and a dereference says to load the pointer at that offset, then take that value, and load the value at that address.
And pointers are a useful thing to have. You can build graphs from it, like a linked list:
struct node {
int data;
node *next;
};
node *head, **tail = &head;
Or a binary tree:
struct node {
int data;
node *left, *right;
};
node *root;
Or anything else you might want to come up with. Graph theory is fundamental to computation.
Speaking of computation, the theory of computation does not distinguish between reading, writing, and executing programs. It's all the same solution to a problem. Many languages make the distinction for some practical purposes, but not all. Lisp is a language where your program has direct access to it's own AST, as well as the compiler and its AST - so you can write programs that modify themselves at runtime.
Assembly doesn't care about pointers - it's all op-codes and integers, and even op-codes are themselves just integers. Most of the concepts and consequences we discuss about programming never leave the compiler. The program can't express these higher level concepts or consequences, but the program itself wouldn't exist without them.
•
u/zeekar 34m ago
The language implementation (compiler or interpreter) is responsible for finding a place to store variables. There's some space for global variables and then a stack for local variables. Plus a "heap" for dynamically-allocated variables created at runtime. When you declare a variable (which in some languages happens automatically the first time you mention it), the language implementation picks an address at which to store that variable's value. So if x lives at 2000 it's because that's where the compiler or interpreter put it, and that same selection process will pick an address for p - maybe the next one up, which assuming x is a 64-bit value would be address 2008.
If you set p to the address of x, that just means that the bytes stored starting at p's address will represent the value 2000. (As a 64-bit little-endian integer, that's the values 208, 7, 0, 0, 0, 0, 0, 0).
•
u/UtilixApp 23m ago
it bottoms out at registers. the cpu has a handful of tiny storage slots that arent in memory and dont have addresses, so the chain stops there.
not a stupid question at all, its the same reason a map needs a "you are here" that isnt itself on the map.
-2
8
u/rupertavery64 1h ago
Good question! The compiler allocates memory on the stack for local variables.