C programming variables stored

Go To Last Post
5 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello, using ATMega32 in assembly if I wanted to create a temporary variable I would pick a register

.def temporaryvariable = r20 ;register 20 will hold a temporary value 

I was a able to debug and view the r20 register and see how the program changes the temporaryvariable value, but what happens when I program in C?

unsigned char temporaryvariable;

Where is this variable stored and can I view it while debugging. Does the compiler assign a register to hold it just like I did in assembly? Or is it somewhere in memory, if so, is there a way to find the address where it is stored?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Depends - if it is a local variable it might be stored in a register or on the stack in ram. If it is a global, in ram. With C, you let the compiler decide for the most part. You can try to force the compiler's hand by using the register declaration, but it is not gauranteed to follow.

How do you know where it is in memory? For the most part you don't care - the compiler and linker manage the grimy details. However, in C there is a concept called the pointer which allows you get the address. If you wanted to mix asm with C, you can reference the variable by name and the linker will resolve the address for you.

The linker can generate a map file that lists the adresses of you functions and data. This information is also encoded in the elf file the debugger loads so you can look at your variables by name.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Where is this variable stored and can I view it while debugging. Does the compiler assign a register to hold it just like I did in assembly? Or is it somewhere in memory, if so, is there a way to find the address where it is stored?

As Kartman says it depends on context. If you write:

uint8_t temporaryvariable; 

int main(void) {
}

the variable is global and the C compiler assigns it to a block of variables called ".bss" (block started by symbol). The linker then places this in RAM after another block of variables called .data.

.data variables are globals that have an initial value assigned and .bss variables are globals (and statics) that have no initial value given (so C guarantees they will hold 0x00 at the start).

This page in the manual and the very useful diagram it contains shows where GCC (in particular) positions .data and .bss in memory:

http://www.nongnu.org/avr-libc/u...

(the 0x0100 in that is for large/modern AVRs, some old/small ones will have RAM that starts at 0x0060)

If you build the above code then look at the .map file you will find (for mega32 with RAM that starts at 0x0060):

 *(.bss)
 *(.bss*)
 *(COMMON)
 COMMON         0x00800060        0x1 test.o
                0x00800060                temporaryvariable
                0x00800061                PROVIDE (__bss_end, .)

The 0x800000 offset in there is just the way GCC handles Harvard architecture memory spaces and offsets RAM addresses from flash addresses.

If you had written:

int main(void) {
  uint8_t temporaryvariable; 

}

then things are different. That is a variable that only exists during the execution of that function. It's known as an "automatic" as it is automatically created when the function is entered and automatically destroyed when execution leaves. To achieve this C compilers locate such variable on the stack. Some use two stacks - one for the CALL/RET addresses (the hardware stack) and one for data such as this (implemented separately as a software stack). GCC on the other hand puts everything on the single stack then uses an index register (Y = R28/R29) to keep track of where such automatics are located - in the generated code you might see a lot of LD/ST Y+n operations. On entry to a function with s6tack based automatics the compiler wil add "prologue" code to the function that adjusts the SP downwards to make room for the automatics and then epilogue code at the end of the function to adjust it back up.

However that's only half the answer (certainly in the case of GCC) as it's an optimising compiler. It will go out of its way to make the code as small and fast as possible (though exactly what it does varies a bit between -O1, -O2, -O3 and -Os). In the example I just quoted the optimiser would recognise that "temporaryvariable" is never accessed within the function so it simply would not be created at all (unless one was unwise enough to use -O0 which means "turn the optimiser off and just do it anyway").

Even if I used it for something:

int main(void) {
  uint8_t temporaryvariable; 

  temporaryvariable = PINB;
  PORTC = temporaryvariable;
}

the optimiser still would not create it in RAM on the stack. It would almost certainly generate this code as:

IN R24, PINB
OUT PORTC, R24

where it's chosen to use R24 (the register it generally starts with) as "temporaryvariable". Because of optimisation it can actually be quite hard to get it to really create the variable in RAM but you can over-ride optimisation using "volatile" so:

int main(void) {
  volatile uint8_t temporaryvariable; 

  temporaryvariable = PINB;
  PORTC = temporaryvariable;
}

will cause the compiler to generate:

int main(void) {
  66:	cf 93       	push	r28
  68:	df 93       	push	r29
  6a:	1f 92       	push	r1
  6c:	cd b7       	in	r28, 0x3d	; 61
  6e:	de b7       	in	r29, 0x3e	; 62
    volatile uint8_t temporaryvariable;

    temporaryvariable = PINB;
  70:	86 b3       	in	r24, 0x16	; 22
  72:	89 83       	std	Y+1, r24	; 0x01
    PORTC = temporaryvariable;
  74:	89 81       	ldd	r24, Y+1	; 0x01
  76:	85 bb       	out	0x15, r24	; 21
}
  78:	80 e0       	ldi	r24, 0x00	; 0
  7a:	90 e0       	ldi	r25, 0x00	; 0
  7c:	0f 90       	pop	r0
  7e:	df 91       	pop	r29
  80:	cf 91       	pop	r28
  82:	08 95       	ret

In this you can see that it starts by preserving Y (R28/R29) and then it does that mysterious PUSH R1. It doesn't really matter what register it pushes at this point (there's no guarantee about the initial state of "temporaryvariable" but what it's doing is creating room to hold temporaryvariable on the stack. It then reads SPH/SPL into the two halves of Y so Y=SP. The variable it has created is therefore at Y+1.

Then you see it read PINB into R24 (that register it often likes to use) and then store that out to RAM at Y+1 where temporaryvariable is stored.

It then immediately reads it back from RAM (to R24) and writes it to PORTC.

At the end of the function the POP R0 is just removing the temporaryvariable space from the stack. Again it doesn't really matter which register is used for the POP but it just so happens that internally GCC uses R0 as __tmp_reg__ as you can see in this alternative view of the code:

__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
	.text
.Ltext0:

	.section	.text.startup.main,"ax",@progbits
.global	main
	.type	main, @function
main:
//==> int main(void) {
	push r28
	push r29
	push __zero_reg__
	in r28,__SP_L__
	in r29,__SP_H__
/* prologue: function */
/* frame size = 1 */
/* stack size = 3 */
.L__stack_usage = 3
//==>     temporaryvariable = PINB;
	in r24,0x16
	std Y+1,r24
//==>     PORTC = temporaryvariable;
	ldd r24,Y+1
	out 0x15,r24
//==> }
	ldi r24,0
	ldi r25,0
/* epilogue start */
	pop __tmp_reg__
	pop r29
	pop r28
	ret

If you think this looks hugely inefficient then you'd be right, it is. This is the reason why volatile should only be used sparingly where it's really required and it's also the reason -O0 should be avoided at all costs because it would lead to the entire program being coded in this way.

As I say, if you drop the volatile what the compiler generates when given the chance to optimise is what you, the Asm programmer would have written anyway:

    temporaryvariable = PINB;
  66:	86 b3       	in	r24, 0x16	; 22
    PORTC = temporaryvariable;
  68:	85 bb       	out	0x15, r24	; 21

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Where is this variable stored and can I view it while debugging
Declare your temporaryvariable global volatile.
In Avrstudio simulator open the "Watch window". Write the name of variable in it.

As others said, in C you need not bother where exactly the variable sits. You always can access it by name.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to note that if the optimiser does choose to just use R24 (or whatever) rather than holding temporaryvariable in RAM that this can sometimes fool watch windows in debuggers. You may find it simply reports something like "variable not in scope" or "variable optimised away" and refuse to show anything. This is one of the few occasions when it might be useful to add an otherwise unnecessary "volatile" as that will force it to exist in RAM and the debugger watch will then have no problem "seeing" it.

If you use "volatile" to make something visible in the debugger this way then remember to remove the volatile when you are finished debugging. The variable will revert to being register only (or possibly even discarded completely) but the code will (well should!) continue to operate identically (if a bit faster and smaller).