Reputation: 91
I'm programming MCUs like STM32F4** and STM32F0**, using ARM GCC noneabi compiler , c/c++ and found interesting pattern.
If I build some *.bin file, it's size is always divisible by 4.
I think it might be because MCU is 32bit (=4 byte). So bin_size%4==0. I have tried some "hacks"; for example enlarge some byte array by 1, but binary size is always the same. When I enlarge array more, binary size is bigger, but again divisible by 4.
Can I consider this effect as axiom?
Or is there some situation when this doesn't work? For example, is it possible somehow switch 32bit STM32 MCU to 16bit mode? Or is possible to create non-divisible binary by 4 with another compiler?
Upvotes: 3
Views: 2651
Reputation: 179991
"For example, is it possible somehow switch 32bit STM32 MCU to 16bit mode"
There is a 16 bits mode, but you may not understand exactly what that implies. To google it, you'd search for "ARM Thumb". That might very well result in *bin files which are a multiple of 2 bytes.
However, "32 bits mode" and "16 bits mode" usually refer to pointer or register sizes, not instruction sizes E.g. the AMD x64 instruction set may be referred to as "64 bits mode", even though its instructions are variable-length. Some x64 instructions are just one byte.
Upvotes: 0
Reputation: 8059
It's possible to create binaries of arbitrary size, the 4 byte alignment is just a matter of convenience. Everyone does it this way, and everyone expects it.
The alignment is enforced in the linker script file. If you look in the *.ld
file of your project, you'll find that there are a lot of
. = ALIGN(4);
statements. They instruct the linker to advance the current output address to a value that's divisible by 4.
So I've created an empty project, and deleted most of the ALIGN
lines from the linker script:
ENTRY(Reset_Handler)
__stack = 0x20014000;
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 80K
}
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} >FLASH
.text :
{
*(.text)
*(.text*)
} >FLASH
.rodata :
{
*(.rodata)
*(.rodata*)
} >FLASH
_sidata = LOADADDR(.data);
.data :
{
_sdata = .;
*(.data)
*(.data*)
_edata = .;
} >RAM AT> FLASH
.bss :
{
_sbss = .;
*(.bss)
*(.bss*)
*(COMMON)
_ebss = .;
} >RAM
}
a minimal program:
void Reset_Handler(void) {
while(1)
;
}
compiled and linked it with -nostartfiles -nodefaultlibs -nostdlib
, to leave out all the standard library stuff. The result is
arm-none-eabi-size --format=berkeley "unaligned.elf"
text data bss dec hex filename
320 0 0 320 140 unaligned.elf
evenly divisible by four. Then I've added a char variable, and did something with it:
volatile char c = 0x42;
void Reset_Handler(void) {
while(1)
c+=1;
}
which resulted in
Invoking: Cross ARM GNU Print Size
arm-none-eabi-size --format=berkeley "unaligned.elf"
text data bss dec hex filename
336 1 0 337 151 unaligned.elf
Finished building: unaligned.siz
The instructions are aligned to 16 bits.
Cortex-M based MCUs like the STM32 series use the Thumb2 instruction set, which is a mix of 16 and 32 bit instructions. It turns out that our first program just happened to have a length divisible by four. I've added a single nop
instruction
void Reset_Handler(void) {
asm("nop");
while(1)
;
}
and the size grew by two bytes, compared to the original:
Invoking: Cross ARM GNU Print Size
arm-none-eabi-size --format=berkeley "unaligned.elf"
text data bss dec hex filename
322 0 0 322 142 unaligned.elf
Finished building: unaligned.siz
Upvotes: 6