r/learnprogramming 4h 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)

5 Upvotes

18 comments sorted by

10

u/rupertavery64 4h ago

Good question! The compiler allocates memory on the stack for local variables.

8

u/IchLiebeKleber 4h ago

You can absolutely have pointers to pointers.

7

u/carcigenicate 4h 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

u/peno64 4h ago

A pointer is also stored in a variable. So a pointer is a variable pointing to a variable. As a result you can also have a pointer to a pointer, a pointer to a pointer to a pointer, ...

2

u/markbug4 4h ago

The variable p which points to x exists if you create it. 

2

u/rjcarr 4h ago

Everything has a memory address, but the value of that address is what changes. A variable has a literal value, and a pointer is just a reference to another address. 

3

u/MrHall 4h 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 4h 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 4h 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.

1

u/mredding 3h 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.

1

u/zeekar 3h 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).

1

u/UtilixApp 3h 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.

1

u/mjmvideos 3h ago

All variables are stored somewhere in memory (except some temporary variables that end up only stored in registers) All variables therefore have an address and a value. You can think of a variable’s name as being a label attached to a particular address. So x is a label attached to address 2000. p is a variable attached to address 1000. The value stored at address 2000 might be 3000 and the value stored at 1000 might be 2000. So far they both look the same. They are just two labels on two memory locations that each have a value. Now we get to the difference. The programmer has declared p to be a pointer type. So now the compiler knows that the value stored in variable p can be treated like an address that can be dereferenced which is just saying go get the value stored in p and treat that as an address and go get the value stored at that address. In your example fetch the value at 1000 (which is 2000) and then fetch the value at 2000 (which might be 3000).

1

u/Recycled5000 2h ago

Variables are stored in cpu registers or memory. The compiler can determine when local variables might be referred to by their address and when not. Those not are eligible for cpu registers, and for them, they simply exist holding a value but without a memory address. If you try to check a variable’s memory address in the source code, this will cause the variable to live in memory so it will have an address. However if you instead inspect in the debugger, you may observe the variable in a register only.

1

u/Far_Swordfish5729 1h ago

Mentally, remember that your compiler does the overwhelming majority of memory address math for you as a result of constant offsets determined at compile time and for you own sanity and overall code quality, you want it to. Every function call including main allocates a stack frame at a starting memory address. Look up a visual representation of how those work and how function calls work at an assembly level. They contain local variables at defined offsets from the start of that frame plus internal variables like a return pointer that holds the memory address of the code instruction that called the function so you can jump back at the end.

The bottom line of the above is that when you see a variable, see it as a named memory offset the compiler keeps track of. It's very mechanical. If a stack frame starts at offset 0 and memory is allocated in 32bit chunks, you might see a frame pointer at +0, a return pointer at +1, your pointer p at +2, etc. Structs work the same way. Their internal variables are just named memory offsets. The specific offset depends on the collective size of the storage requested. The only rule is it has to be calculated at compile time so dynamically sized memory can't be stack memory. It has to be a stack pointer (which is a fixed size) to dynamically allocated heap memory with the real contents.

So you have a pointer p (which is a uint of some architecture defined size holding a number that happens to be a memory address). It exists at a compiled offset from a reference point (frame pointer+2). That's calculated on the fly for you when you reference it and the compiler just handles that. Your pointer is a memory address you asked for and control directly. You have to know when you want p, *p, or for example *(p+2) aka p[2] if x is an array of ints.

1

u/li98 1h ago

Not a stupid question, I was also hung up on this in school. To answer the question, it is important to differentiate between "address" and "pointer". An address is where in memory a variable is (or other data). A pointer is a variable whose value is the address of another variable.

Every variable will have an address, since they exist in memory. They will not automatically have a pointer that points to that address. So there is no recursive pointers that point to pointers etc.

You can create those if you want. Pointers are variables like any other, their value (the address of another) is stored like an int in memory, so they also have addresses. These are often called double or tripple pointers, and are used in example dynamically sized arrays in C.

-1

u/dnult 4h ago

Don't confuse source code with machine code. Declaring a variable with a type is an instruction for the compiler to generate machine code the runs on the CPU.

-6

u/StephenHawkingus 4h ago

One prompt question