SrcForge

Lesson 41

Inline Assembly

Inline Assembly allows assembly instructions to be embedded directly inside a C program. GCC Extended Inline Assembly supports input operands, output operands, and clobber lists that help the compiler integrate assembly safely with surrounding C code. It is used in operating system kernels, embedded systems, device drivers, and performance-critical routines where standard C cannot access processor-specific features directly.

C Track

Save progress as you learn.

44 lessons

Lesson content

Inline Assembly in C Programming: A Complete Beginner to Advanced Guide

What is Inline Assembly in C Programming?

Although C is considered a low-level programming language, there are situations where developers need even more direct control over the processor. Tasks such as accessing CPU-specific instructions, optimizing performance-critical code, interacting with hardware, or implementing operating system kernels often require writing assembly language.

Inline Assembly (also called Inline ASM) allows you to write assembly instructions directly inside a C program. Instead of creating a separate assembly source file, you can embed assembly code within C functions.

Think of C as driving a car with a steering wheel and pedals, while assembly language is like controlling each engine component manually. Inline Assembly lets you temporarily switch to manual control whenever you need maximum precision.

Why Does Inline Assembly Exist?

Without Inline Assembly, some processor instructions cannot be accessed directly, hardware-specific operations become difficult, and certain performance optimizations are impossible. With Inline Assembly, you can access CPU registers directly, execute processor-specific instructions, optimize performance-critical code, implement low-level system software, and communicate directly with hardware.

Real-World Use Cases

Inline Assembly is commonly used in:

  • Operating system kernels
  • Embedded systems
  • Device drivers
  • Bootloaders
  • Cryptography
  • High-performance computing
  • CPU instruction testing
  • Interrupt handling
  • Context switching
  • Hardware control

Prerequisites

Before learning Inline Assembly in C programming, you should understand:

  • Basic C programming
  • Variables and data types
  • Functions
  • Pointers
  • Memory layout
  • CPU registers
  • Basic assembly language concepts
  • GCC compiler usage

Core Concepts

What is Inline Assembly?

Inline Assembly allows assembly instructions to be embedded directly inside C code. Both forms below are accepted by GCC:

asm("assembly instructions");

__asm__("assembly instructions");

GCC Extended Inline Assembly Syntax

asm (
    "assembly instructions"
    : output_operands
    : input_operands
    : clobbered_registers
);
ComponentDescription
Assembly TemplateAssembly instructions as a string
Output OperandsVariables modified by assembly
Input OperandsVariables passed to assembly
Clobber ListRegisters or memory modified by assembly

Basic Assembly Instructions

InstructionPurpose
`mov`Move data
`add`Add values
`sub`Subtract values
`mul`Multiply
`div`Divide
`inc`Increment
`dec`Decrement
`cmp`Compare
`jmp`Jump
`nop`No operation

Operand Constraints

Constraints tell GCC where operands may be placed.

ConstraintMeaning
`"r"`General-purpose register
`"m"`Memory location
`"i"`Immediate constant
`"g"`Register or memory
`"0"`Same location as first output operand
asm("add %1, %0"
    : "+r"(a)
    : "r"(b));

Clobber List

The clobber list informs GCC about registers or resources modified by the assembly code. Common clobbers include "memory", "cc" (condition flags), and register names such as "eax".

asm(
    "nop"
    :
    :
    : "memory"
);

Volatile Assembly

Use volatile when the compiler must not optimize away the assembly block. This is useful for hardware interaction and timing-sensitive operations.

asm volatile("nop");

Code Examples

Example 1: Beginner – Executing a NOP Instruction

#include <stdio.h>                  // Standard input/output library

int main()                          // Program entry point
{
    asm volatile("nop");            // Execute a No Operation instruction

    printf("NOP executed.\n");      // Display message

    return 0;                       // Exit successfully
}

Example 2: Intermediate – Adding Two Numbers

#include <stdio.h>                  // Standard I/O library

int main()                          // Program entry point
{
    int a = 10;                     // First operand

    int b = 20;                     // Second operand

    asm(
        "add %1, %0"                // Add second operand to first
        : "+r"(a)                   // Output/Input operand
        : "r"(b)                    // Input operand
        : "cc"                      // Condition codes modified
    );

    printf("%d\n", a);              // Display result

    return 0;                       // Exit program
}

Example 3: Intermediate – Swapping Two Values

#include <stdio.h>                  // Standard I/O library

int main()                          // Program entry point
{
    int x = 5;                      // First value

    int y = 10;                     // Second value

    asm(
        "xchg %0, %1"               // Exchange values
        : "+r"(x), "+r"(y)          // Output/Input operands
        :                           // No additional inputs
        :                           // No clobbers
    );

    printf("x = %d\n", x);          // Display new x

    printf("y = %d\n", y);          // Display new y

    return 0;                       // Exit program
}

Example 4: Advanced – Reading the Time Stamp Counter (x86)

#include <stdio.h>                  // Standard I/O library
#include <stdint.h>                 // Fixed-width integer types

int main()                          // Program entry point
{
    uint32_t low;                   // Lower 32 bits

    uint32_t high;                  // Upper 32 bits

    asm volatile(
        "rdtsc"                     // Read Time Stamp Counter
        : "=a"(low), "=d"(high)     // Outputs from EAX and EDX
    );

    unsigned long long ticks =
        ((unsigned long long)high << 32) | low; // Combine values

    printf("%llu\n", ticks);        // Display CPU ticks

    return 0;                       // Exit program
}

Example 5: Advanced – Using CPUID (x86)

#include <stdio.h>                  // Standard I/O library

int main()                          // Program entry point
{
    unsigned int eax = 0;           // Input value

    unsigned int ebx;               // Output register

    unsigned int ecx;               // Output register

    unsigned int edx;               // Output register

    asm volatile(
        "cpuid"                     // Execute CPUID instruction
        : "=a"(eax), "=b"(ebx),
          "=c"(ecx), "=d"(edx)      // Output registers
        : "a"(eax)                  // Input register
    );

    printf("CPU Vendor Registers:\n"); // Display heading

    printf("EBX = %u\n", ebx);      // Print EBX

    printf("ECX = %u\n", ecx);      // Print ECX

    printf("EDX = %u\n", edx);      // Print EDX

    return 0;                       // Exit program
}

Common Mistakes and Pitfalls

WrongCorrect
Forgetting operand constraintsSpecify proper input and output constraints
Omitting clobbered registersList all modified registers and flags
Writing architecture-specific code without documentationClearly document target architecture
Using Inline Assembly for ordinary arithmeticPrefer normal C unless assembly is required
Ignoring compiler optimizationsUse volatile only when necessary

Wrong:

asm("add eax, ebx");

Correct:

asm(
    "add %1, %0"
    : "+r"(a)
    : "r"(b)
    : "cc"
);

Best Practices

  • Use Inline Assembly only when standard C cannot accomplish the task.
  • Prefer compiler intrinsics when available as they are usually more portable and easier to optimize.
  • Clearly document architecture-specific instructions.
  • Keep assembly blocks as small and focused as possible.
  • Specify accurate input, output, and clobber lists.
  • Use volatile only when preventing optimization is necessary.
  • Test code with different optimization levels.
  • Verify compatibility across compiler versions and CPU architectures.
  • Review the generated assembly with gcc -S to understand compiler behaviour.
  • Keep portability in mind, especially for projects targeting multiple platforms.

When NOT to Use This

Inline Assembly is not the best choice when:

  • Standard C provides an efficient solution.
  • Compiler intrinsics expose the required CPU instruction.
  • Portability across different CPU architectures is important.
  • Readability and maintainability are higher priorities than low-level optimization.
  • The performance benefit is insignificant compared to the added complexity.

Summary / Key Takeaways

  • Inline Assembly embeds assembly instructions directly within C code.
  • GCC Extended Inline Assembly supports input operands, output operands, and clobber lists.
  • Operand constraints help the compiler allocate registers and memory correctly.
  • volatile prevents the compiler from removing essential assembly instructions.
  • Inline Assembly is useful for hardware interaction, processor-specific instructions, and performance-critical routines.
  • It should be used sparingly because it reduces portability and increases maintenance complexity.
  • Prefer compiler intrinsics or standard C whenever they provide equivalent functionality.

FAQ About Inline Assembly in C

1. What is Inline Assembly in C programming?

Inline Assembly allows assembly language instructions to be written directly inside a C program, enabling access to processor-specific features and low-level operations.

2. Why should I use Inline Assembly?

It is useful when you need CPU-specific instructions, direct hardware access, or highly optimized routines that cannot be expressed efficiently in standard C.

3. What is the purpose of operand constraints?

Operand constraints tell the compiler where operands may be stored such as registers or memory and how they are used, allowing it to integrate the assembly block safely with surrounding C code.

4. What does asm volatile mean?

volatile tells the compiler that the assembly block has important side effects and must not be removed or reordered during optimization.

5. Is Inline Assembly portable?

No. Inline Assembly is generally compiler- and architecture-specific. Code written for GCC on x86 processors may not compile or work correctly on ARM processors or with a different compiler.