How to Use Inline Assembly in C Programming
Inline assembly allows developers to embed architecture-specific
assembly instructions directly into C source code without creating
separate assembly files. This article explains how C compilers integrate
inline assembly using the asm keyword, details the
structure of basic and extended assembly blocks, and outlines how the
compiler manages registers, memory, and code optimization during the
compilation process.
The asm Keyword
In standard C and compiler extensions like GCC, Clang, and MSVC,
inline assembly is invoked using the asm or
__asm__ keyword. The compiler treats the content inside the
assembly block as raw instructions that bypass the high-level language
translation, placing them directly into the instruction stream emitted
by the compiler.
Depending on the compiler and platform, inline assembly is implemented in two main styles:
- MSVC (x86 only): Uses block format
(
__asm { ... }) where instructions are written naturally and the compiler resolves C variable names automatically. - GCC / Clang: Uses a string-based format
(
asm("...")) supporting both Basic Inline Assembly and Extended Inline Assembly.
Basic vs. Extended Inline Assembly
Basic inline assembly simply passes instruction strings to the assembler without interacting with C variables:
__asm__("nop");Extended inline assembly allows data exchange between C expressions and assembly instructions using a standardized four-part syntax:
__asm__ (
"assembly template"
: output operands
: input operands
: clobbered registers
);- Assembly Template: A string containing the assembly
instructions. The compiler substitutes tokens like
%0,%1with the physical registers or memory locations mapped to the operands. - Output Operands: Expressions where the results of
the assembly instructions are stored, prefixed by constraint modifiers
(e.g.,
"=r"for write-only general-purpose register). - Input Operands: C variables or constants passed
into the instructions, specified with constraint letters (e.g.,
"r"for general register,"m"for memory). - Clobber List: A list of registers, condition codes
(
"cc"), or memory ("memory") altered by the assembly instructions. This tells the compiler's register allocator not to assume these values are preserved.
How the Compiler Processes Inline Assembly
The C compiler does not validate whether the assembly instructions are semantically correct. Instead, it follows these integration steps:
- Register Allocation: The compiler inspects the input and output constraints and maps C variables to appropriate hardware registers or stack locations.
- Template Substitution: It replaces operand
placeholders (such as
%0) with the assigned hardware registers. - Instruction Emission: The processed assembly string
is inserted into the compiler’s intermediate assembly output
(
.sfile). - Assembler Execution: The native assembler (e.g.,
GNU
as) parses and translates the final assembly text into machine code within the object file (.o).
Preserving Assembly from Optimization
Optimizing compilers routinely remove dead code or reorder
instructions. To prevent the compiler from moving, altering, or
eliminating an assembly block that performs critical tasks (like
hardware I/O or processor state changes), the volatile
qualifier is used:
__asm__ __volatile__ (
"cli" ::: "memory"
);The volatile keyword guarantees that the compiler
preserves the assembly block exactly where it appears in the control
flow.