In this lesson, you will learn how to bridge the gap between high-level C logic and machine-executable instructions by generating and inspecting compiler-produced assembly files.
Why View the Assembly?
While we write C code to maintain portability and readability, the embedded processor executes raw machine instructions. When optimizing for performance-critical tasks—such as ensuring a DMA transfer trigger occurs with minimal jitter or verifying that a critical section is truly atomic—you cannot rely on the C source alone. Generating assembly allows you to see exactly how your compiler interpreted your instructions, how it utilized registers, and whether it introduced unnecessary memory overhead.
Generating the Output
Most compilers, such as GCC or Clang, provide a flag to stop the compilation process after the assembly generation stage. For GCC, you use the -S flag:
arm-none-eabi-gcc -S -O2 main.c -o main.s
When you open main.s, you will see a text file containing architecture-specific instructions (e.g., ARM Thumb-2). Instead of raw binary, you see labels, mnemonics like LDR, STR, and ADD, and directives that define stack frame alignment.
Analyzing the Translation
Consider a simple C operation: *ptr = *ptr + 1;. You might expect a single operation, but the assembly output reveals the reality of the load-store architecture:
ldr r0, [r1] @ Load value from memory address in r1 to r0
add r0, r0, #1 @ Increment the value in register r0
str r0, [r1] @ Store the value back to memory address in r1
By examining this, you can verify if the compiler performed optimizations like "loop unrolling" or if it incorrectly spilled variables to the stack instead of keeping them in general-purpose registers. This visibility is essential when verifying that your code adheres to strict timing requirements or that it safely interacts with memory-mapped peripherals without triggering unintended bus contention.
Key Takeaway: Generating assembly files provides a transparent view of how your high-level code is transformed into machine instructions, allowing you to audit, debug, and optimize your implementation at the hardware abstraction level.