Maybe, maybe not - depending on your assumptions, and the details of the
code.
Many processors support unaligned access, meaning the code size will be
the same even if "packed" results in having unaligned accesses. In this
case, code size is the same but execution time may be lower for the
actual access. The Cortex-M4, for example, is fine with unaligned access.
The Cortex-M0+, on the other hand, does not support it. So instead of a
single 32-bit load, you can quickly end up with a mess:
#include <stdint.h>
typedef struct {
uint32_t b;
} su;
typedef struct {
uint32_t b;
} __attribute__((packed)) sp;
int foou(su * p) { return p-> b; }
int foop(sp * p) { return p-> b; }
foou:
ldr r0, [r0]
bx lr
foop:
ldrb r2, [r0, #1]
ldrb r1, [r0]
ldrb r3, [r0, #2]
lsls r2, r2, #8
ldrb r0, [r0, #3]
orrs r1, r2
lsls r3, r3, #16
orrs r3, r1
lsls r0, r0, #24
orrs r0, r3
bx lr
This is big, slow, and may be incorrect if you expected atomic loads and
stores.
I have very rarely seen "packed" used in a way that is actually useful.
Often when people think they need to pack a struct, they can get better
results with more careful re-arrangement of the fields, or perhaps
"manually" packing with bit-fields or other ways of combining fields.
Another alternative is that instead of having an array of structs, you
can split the data up into two or three arrays each containing part of
the data. (This is often done on big systems with cache, as it can lead
to massive speed increases.)
Throwing "packed" onto structs is one of the last places I would look
for some extra ram space, not one of the first.