Misaligned accesses (ie accessing a N bit variable on something other than a N bit memory boundary) tend to be either prohibited or slow, so compilers avoid generating them. As a consequence data structures are not packed- and if you don't arrange things efficiently the compiler will generate data structures with holes.
E.g.
BAD
struct {
a uint32_t,
b uint8_t,
c uint32_t,
d uint8_t,
e uint32_t,
}
GOOD:
struct {
a uint32_t,
c uint32_t,
e uint32_t,
b uint8_t,
d uint8_t,
}
The latter case uses less RAM for the same data. So that's one thing to watch out for. Use sizeof() to check.
Also - Compilers generally generate the most concise code when dealing with variables that match the natural word size of the CPU- so use something that makes life easy for the compiler.
BAD:
unsigned char i;
for (i = 0; i < 100; i ++) {
// blah
}
GOOD:
int i;
for (i = 0; i < 100; i ++) {
// blah
}
i is a stack variable - and is probably going to have the same stack usage in either case.
The code may be smaller (.text) because it's all 32 bit ops. ARM does have 8/16/32 bit ops,
so may do an ok job with this - but the point is that picking a local variable that is smaller in
size is really a no-op as far as data and code size go.
int
signed integer - size is compiler dependent, 32 bits on the ARM cortex M4
int8_t,int16_t,int32_t
These are also signed integers, but the size is explicit.
min_value = -(1 << (n - 1))
max_value = (1 << (n-1)) - 1
where n is the size in bits.
e.g. in python
n = 8
min_value = -(1 << (n - 1))
print min_value
-128
max_value = (1 << (n-1)) - 1
print max_value
127
BTW - if you really want to save RAM you should look at the *.map file and see where it's being used.
That'll tell you where you need to focus your efforts.