You're asking about how local variables obtain their addresses.
Local variables are placed onto a runtime stack. This is done dynamically: as each function is called, it is activated by creating a fresh, new copy of its stack frame; this copy is effectively pushed onto the stack, so its location has to do with both the location of the stack, and, what else (other functions) were on the stack at the time — i.e. the dynamic call chain.
The stack frame for a function is an object whose layout is setup by the compiler or assembly language programmer, somewhat similar to other objects like structs or classes (that are local variables or go in the heap). Variables like
a in the function are like fields in this stack frame object.
Note that this dynamic stack mechanism naturally supports recursion: one function can be activated multiple times, meaning that a local variable could occur in multiple locations in the stack in the case of recursion. This should emphasize the dynamic nature of the stack frame activation record.
The location of the runtime stack itself is determined by the operating system; it is allocated as an object in the address space of the process.
In any case, these are all just data structures; some are like classes (i.e. metadata describing data, fields, and their offsets; and others are like instances): the stack is a data structure in the process; the stack frame is a description of an activation record; the activation record is a data structure within the stack. A variable (like
a) is a field in the activation record.
As you can see, one data structure can exist within another (stack within a process, stack frame within a stack). Addresses of individual fields and variables are created via the outer data structure's location, and the field or inner data structure's offsets within that data structure, and so on, recursively.
The same is true whether the thread is a kernel thread or user thread.