Why Mix Assembly and C at All
Assembly and C are mixed for two practical reasons: performance-critical kernels (checksum loops, cryptographic primitives, SIMD-heavy math) that the compiler cannot express as efficiently as hand-tuned code, and low-level glue that C cannot express at all, such as a syscall wrapper, a context-switch routine, or a bootloader entry point. Rather than writing an entire program in assembly, real-world code isolates the assembly to a small number of functions and lets C handle everything else — parsing, memory management, application logic — calling into and out of the assembly only at well-defined function boundaries.
Cricket analogy: It is like a Test team that plays with specialist bowlers who come on only for short, high-impact spells — say, a strike bowler brought in purely to attack the tail — rather than bowling every single over of the innings themselves.
The System V AMD64 Calling Convention
On Linux, macOS, and other System V systems, integer and pointer arguments are passed in RDI, RSI, RDX, RCX, R8, R9 in order, floating-point arguments go in XMM0 through XMM7, and the return value comes back in RAX (or RAX:RDX for 128-bit values, or XMM0 for floats). RBX, RBP, and R12–R15 are callee-saved, meaning an assembly function that uses them must push and restore their original values before returning; RAX, RCX, RDX, RSI, RDI, R8–R11 are caller-saved and may be freely clobbered. Crucially, the stack must be 16-byte aligned at the point of a CALL instruction, so RSP+8 is 16-byte aligned immediately inside the callee — getting this wrong causes crashes in functions that use aligned SSE loads like MOVAPS.
Cricket analogy: It is like a fixed batting lineup contract: the top six slots (RDI through R9) are pre-assigned to specific batters, but any fielder in the deep (caller-saved registers) can be freely rotated between overs while the wicketkeeper and slip cordon (callee-saved registers) must return to their exact original positions.
Calling C From Assembly, and Assembly From C
To call a C function like printf from NASM, you declare it with extern printf, load arguments into the ABI registers described above, ensure the stack is 16-byte aligned, and issue CALL printf — variadic functions additionally require AL to hold the count of vector registers used for floating-point arguments. To expose an assembly function to C, you declare it global my_asm_func, give it a matching prototype in a C header (extern "C" if compiled as C++, to suppress name mangling), and make sure it respects the callee-saved register contract and returns its result in RAX/XMM0 exactly as C expects. Getting either direction wrong — misaligned stack, clobbered callee-saved registers, or forgetting AL for variadics — produces crashes that are notoriously hard to trace because the corruption surfaces far from its actual cause.
Cricket analogy: It is like an overseas player joining an IPL franchise: they must register under the exact squad rules (function prototype), follow the team's fielding restrictions (calling convention), and the scorer must log their contribution under their registered name, not a nickname (name mangling).
; NASM: call printf("sum = %d\n", a + b) then return the sum to a C caller
extern printf
global add_and_print
section .rodata
fmt db "sum = %d", 10, 0
section .text
add_and_print: ; int add_and_print(int a, int b) -- a in edi, b in esi
push rbx ; callee-saved register we intend to use
mov ebx, edi
add ebx, esi ; ebx = a + b
; align stack to 16 bytes before CALL (RSP was 16-aligned on entry to this
; function per the ABI, then push rbx made it 8 mod 16, so no extra push needed here)
mov edi, fmt
mov esi, ebx
xor eax, eax ; AL = 0 vector registers used (printf is variadic)
call printf
mov eax, ebx ; return sum in eax
pop rbx
retThe 128-byte 'red zone' below RSP on System V AMD64 is a scratch area a leaf function (one that makes no further calls) may use without adjusting RSP at all. It is a free lunch for small leaf assembly routines but must not be used by any function that itself calls out, since a CALL instruction can overwrite it via signal handlers or nested calls.
Forgetting to keep RSP 16-byte aligned before a CALL to a C function is one of the most common assembly-C interop bugs. It rarely crashes immediately; instead it corrupts SSE-aligned local variables inside the callee, producing intermittent segmentation faults that seem unrelated to the actual misalignment.
- Assembly is typically mixed with C for hot-path performance or for operations C cannot express, like raw syscalls or context switches.
- System V AMD64 passes integer arguments in RDI, RSI, RDX, RCX, R8, R9 and floats in XMM0–XMM7.
- RBX, RBP, and R12–R15 are callee-saved; RAX, RCX, RDX, RSI, RDI, R8–R11 are caller-saved.
- The stack must be 16-byte aligned at every CALL instruction, or SSE-aligned loads in the callee can fault.
- Variadic C functions like printf require AL to hold the number of vector registers used for float arguments.
- extern declares an external C symbol to call; global exposes an assembly symbol for C to call.
- The 128-byte red zone lets leaf functions use stack space below RSP without adjusting the pointer.
Practice what you learned
1. In the System V AMD64 calling convention, which register holds the first integer argument?
2. Which of these registers is callee-saved under System V AMD64?
3. What must AL contain when calling a variadic C function like printf from assembly?
4. What is the required stack alignment at the point of a CALL instruction under System V AMD64?
5. What is the red zone in the System V AMD64 ABI?
Was this page helpful?
You May Also Like
System Calls Explained
How assembly programs ask the operating system kernel to perform privileged work like reading files, allocating memory, and exiting a process.
Debugging Assembly with GDB
Practical GDB workflows for stepping through raw instructions, inspecting registers and memory, and setting breakpoints in assembly-level code.
SIMD and Vector Instructions
How SSE and AVX let a single instruction operate on multiple data elements at once, and the alignment and transition rules that govern using them correctly.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics