Minimizing memory space for constant "data"

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

I have a constant value in my C code that the compiler can compute at compile-time, but it still takes up space (and is initialized by startup code) in SRAM. I would like to find a way to keep it out of SRAM and EEPROM, yet also avoid resorting to the PROGMEM attribute (and associated pgmspace.h Program Space Utilities to access it).

This obvious approach suffices for the simple case:

#define CONSTANT 3;
uint8_t i;
i = CONSTANT;

Since it's value is known at compile time, the compiler treats CONSTANT as a literal, and emits 3 with the program instruction. Also, no SRAM is reserved for CONSTANT.

My problem is that I want to use a macro function to declare my constant (which didn't compile as I soon discovered) like this:

(global file scope)

#define DECLARE(name, c) \
   uint8_t name##_foo; \
   uint8_t name##_bar; \
   #define name##_constant c   /* Produces compiler error */
#define USE(name) \
   if (name##_foo > name##_constant) \
      ++name##_bar

DECLARE(var1, 1)
DECLARE(var2, 2)
DECLARE(var3, 3)

void f()
{
   USE(var1)
   USE(var2)
   USE(var3)
}

I made the compiler happy with

(global file scope)

#define DECLARE(name, c) \
   uint8_t name##_foo; \
   uint8_t name##_bar; \
   const name##_constant = c; /* OK inside macro */
#define USE(name) \
   if (name##_foo > name##_constant) \
      ++name##_bar

DECLARE(var1, 1)
DECLARE(var2, 2)
DECLARE(var3, 3)

void f()
{
   USE(var1)
   USE(var2)
   USE(var3)
} 

which works, but WinAVR puts name##_constant in initialized SRAM (startup code initializes it). Interestingly, since the compiler knows the value of name##_constant at compile time, it "optimizes-out" the SRAM reference and emits the literal value with the instruction, just like the simple case at the beginning. Other than startup code initialization, the emitted code never accesses the allocated SRAM location.

So, I made the compiler happy, but wasted SRAM (and some program flash for initialization).

My next approach was to try out the program space utilities in pgmspace.h, like this:

#include 

const uint8_t PROGMEM xxx_constant = 3;

void f()
{
   uint8_t i = pgm_read_byte(xxx_constant);
}

This avoided using SRAM, but each reference to name##_constant required a bunch of code.

My last approach (I thought of it while writing this) was to declare the constant in program space, but access it directly (not using pgmspace.h utilities), relying on the compiler to figure out what I wanted:

#include 

const uint8_t PROGMEM xxx_constant = 3;

void f()
{
   uint8_t i = xxx_constant;  /* literal 3 emitted for xxx_constant */
}

Well, it worked as hoped; the compile time computed constant value was emitted with the instruction as in the simple case. The only caveat is that I needed to enable optimizations (I only tried -Os).

Without optimization (-O0), the address of the constant in program space was emitted, which would not work.

I investigated using templates, but it looked like they didn't work in C.

Does anyone have any ideas how to solve the original problem (and still allow me to be lazy by using macros?)

Last Edited: Mon. Jun 25, 2012 - 12:42 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

yet also avoid resorting to the PROGMEM attribute (and associated pgmspace.h Program Space Utilities to access it).

Why? Surely this the whole point of PROGMEM??

But the optimiser should arrange for any compile time constants to not generate the variables anyway

uint8_t i = 5;
PORTB = i;

should compile to:

LDI R24, 5
OUT 0x17, R24

such that 'i' only notionally ever exists as R24 but no SRAM is consumed.

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

flubberlab wrote:
I made the compiler happy with

   ...
   const uint8_t name##_constant = c

which works, but WinAVR puts name##_constant in initialized SRAM (startup code initializes it).

Because the compiler can't know whether this constant is also accessed from a different module. Make it "static".

Stefan Ernst

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

sternst wrote:
Make it "static".

Sorry, I didn't mention it, but it already is. (I edited to post to clarify that. Thanks)

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

Quote:

(I edited to post to clarify that. Thanks)

I don't see static in those definitions above? Also can you be clear as to whether you are talking about variables with local or global name scope. I read the indentation in your code sequences as an implication that things were local but clearly Stefan did not.

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

Quote:
Why? Surely this the whole point of PROGMEM??

But the optimiser should arrange for any compile time constants to not generate the variables anyway

Perhaps I misunderstand the use and capabilities of PROGMEM, and its associated access utilities.

I wanted to avoid the overhead code (pgm_read_byte()) that accesses the declared constant.

Yes, the compiler optimized it down to emitting the computed literal, but (of course) only if I avoided the pgm_read_byte() by referencing the constant directly:

const uint8_t PROGMEM name##_constant = c;
uint8_t i = name##_constant; 

I thought doing it like this was a kludge, and that a PROGMEM-attributed variable was only supposed to be accessed via pgm_read_xxx.

Is it safe/fair to access directly, as above, without pgm_read_xxx?

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

flubberlab wrote:
sternst wrote:
Make it "static".

Sorry, I didn't mention it, but it already is. (I edited to post to clarify that. Thanks)

No, it isn't.

You have to explicitly tell the compiler that the constant can't be accessed from a different module by putting a "static" into the definition.

Stefan Ernst

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

Sorry for the sloppy example code. I edited the original post make it more clear (hopefully).

The actual result remains the same.

Thanks.

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

flubberlab wrote:
The actual result remains the same.
I tried your code and no SRAM was allocated for the "static const" constant.

Data:          6 bytes (0.6% Full)

Stefan Ernst

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

flubberlab

Can you simply post a 5..10 line complete, compilable program that shows the problem you are trying to solve? If I correct a couple of errors in your previous code this compiles:

#include 

#define DECLARE(name, c) \
uint8_t name##_foo; \
uint8_t name##_bar; \
static const uint8_t name##_constant = c; /* OK inside macro */
#define USE(name) \
if (name##_foo > name##_constant) \
++name##_bar;

DECLARE(var1, 1)
DECLARE(var2, 2)
DECLARE(var3, 3)

void f()
{
	USE(var1)
	USE(var2)
	USE(var3)
}

int main(void) {
	
}

After pre-processing this yields:

uint8_t var1_foo; uint8_t var1_bar; static const uint8_t var1_constant = 1;
uint8_t var2_foo; uint8_t var2_bar; static const uint8_t var2_constant = 2;
uint8_t var3_foo; uint8_t var3_bar; static const uint8_t var3_constant = 3;

void f()
{
 if (var1_foo > var1_constant) ++var1_bar;
 if (var2_foo > var2_constant) ++var2_bar;
 if (var3_foo > var3_constant) ++var3_bar;
}

int main(void) {

}

The RAM usage is 6 bytes in .bss (varN_foo's and varN_bar's). The code generated is:

f:
.LFB0:
	.file 1 ".././GccApplication1.c"
	.loc 1 18 0
	.cfi_startproc
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
	.loc 1 19 0
	lds r24,var1_foo	 ;  var1_foo, var1_foo
	cpi r24,lo8(2)	 ;  var1_foo,
	brlo .L2	 ; ,
	.loc 1 19 0 is_stmt 0 discriminator 1
	lds r24,var1_bar	 ;  var1_bar, var1_bar
	subi r24,lo8(-(1))	 ;  tmp53,
	sts var1_bar,r24	 ;  var1_bar, tmp53
.L2:
	.loc 1 20 0 is_stmt 1
	lds r24,var2_foo	 ;  var2_foo, var2_foo
	cpi r24,lo8(3)	 ;  var2_foo,
	brlo .L3	 ; ,
	.loc 1 20 0 is_stmt 0 discriminator 1
	lds r24,var2_bar	 ;  var2_bar, var2_bar
	subi r24,lo8(-(1))	 ;  tmp56,
	sts var2_bar,r24	 ;  var2_bar, tmp56
.L3:
	.loc 1 21 0 is_stmt 1
	lds r24,var3_foo	 ;  var3_foo, var3_foo
	cpi r24,lo8(4)	 ;  var3_foo,
	brlo .L1	 ; ,
	.loc 1 21 0 is_stmt 0 discriminator 1
	lds r24,var3_bar	 ;  var3_bar, var3_bar
	subi r24,lo8(-(1))	 ;  tmp59,
	sts var3_bar,r24	 ;  var3_bar, tmp59
.L1:
	ret

The constants themselves are hard-coded there in the code - they do not exist in .data

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
You have to explicitly tell the compiler that the constant can't be accessed from a different module by putting a "static" into the definition.

You're right (but you already knew that...).

It works great with 'static const'.

(I erroneously thought that everything declared at file scope was 'static' by definition.)

Thank you.

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

Quote:
Can you simply post a 5..10 line complete, compilable program that shows the problem you are trying to solve?

Absolutely right... You have ended up doing more work than I!

The 'static const' solves the SRAM allocation if optimization is turned on.

Thanks clawson and sternst solving this, and helping me to learn how to post/present a question more effectively.