Search code examples
variablesauto

How does C reference auto variables.


I'm trying to understand how the compiler references auto variables. My point of reference is the PIC32MZ microcontroller with the XC32 compiler.

So far I understand how global and static variables work. The compiler lays them out in memory and then hard codes the address and size directly into the code. The instructions would contain the addresses. So an operation like c = a + b, if they all were globals, would look something like. (sudo assembly)

Move address of a to working register. Move address of b to another working register. Add the two working register. Move the result to address of c.

How does this work with auto variables?

From reading the XC32 compiler manual, the auto variables are stored on the stack. The end of the stack is kept track of by the stack pointer. It makes no mention of how they are referenced later. What address or command would the instruction contain if the location of the variable in memory changes?

With dynamic memory the idea makes perfect sense. The return of malloc is a pointer to the location of the new variable and that pointer is stored in an already existing variable.


Solution

  • I found the answer I was looking for by looking at the disassembly of some code that I wrote. The general idea is as expected but the details I found surprising.

    Here is a small snippet of the beginning of the function and the end.

    Beginning of function

    0x9D005640: ADDIU SP, SP, -56
    0x9D005644: SW RA, 52(SP)
    0x9D005648: SW S7, 48(SP)
    0x9D00564C: SW S6, 44(SP)
    0x9D005650: SW S5, 40(SP)
    0x9D005654: SW S4, 36(SP)
    0x9D005658: SW S3, 32(SP)
    0x9D00565C: SW S2, 28(SP)
    0x9D005660: SW S1, 24(SP)
    0x9D005664: SW S0, 20(SP)
    

    End of function

    0x9D0057CC: LW RA, 52(SP)
    0x9D0057D0: LW S7, 48(SP)
    0x9D0057D4: LW S6, 44(SP)
    0x9D0057D8: LW S5, 40(SP)
    0x9D0057DC: LW S4, 36(SP)
    0x9D0057E0: LW S3, 32(SP)
    0x9D0057E4: LW S2, 28(SP)
    0x9D0057E8: LW S1, 24(SP)
    0x9D0057EC: LW S0, 20(SP)
    0x9D0057F0: JR RA
    0x9D0057F4: ADDIU SP, SP, 56
    

    In the beginning, the Stack Pointer (SP) is incremented to give more room on the stack. Since the stack starts at the upper addresses and goes down it makes sense that 56 is subtracted from the stack pointer to move it. This is done with the ADDIU command and by adding a negative value.

    Then comes the surprising part. The SW command moves data from working registers to memory. As can be seen S0 through S7 is copied. From what the MIPS documentation says, the S working registers need to be emptied before they are used. This behavior then aligns with the documentation.

    The SW command moves data from a working register to a memory address. This is where my question is answered. The listed address is for example 20(SP). This means SP + 20. This mechanism then allows for addressing data relative to SP. By adding to SP, this moves the data address into the stack relative to the stack pointer. The 20(SP) routine is like a subcommand because the core calculates SP + 20 before executing SW.

    Then when the function performs its operations it uses the now free working resisters.

    Finally, when operations are complete the data that is sitting in the stack is moved back into the working registers with the LW command. Similar to the SW command the addressing from the stack is done with relative offsets. To wrap up the function 56 is added back to the stack pointer to release the memory that was being used.