In the study of computer science, understanding how high-level languages are translated into instructions that machines can execute is a core skill. This article will analyze the correspondence between a specific C function and its corresponding x86-64 assembly code example, revealing the underlying workings of computers.
Starting with a C Function
The example is taken from the classic book “Computer Systems: A Programmer’s Perspective”. The C code defines a function named <span>exchange</span>. This function takes two parameters: a pointer to a long integer data <span>xp</span> and a long integer value <span>y</span>. Within the function body, it first reads the value from the memory address pointed to by the pointer <span>xp</span> and stores it in a local variable <span>x</span>. Then, it writes the value of the parameter <span>y</span> to the memory address pointed to by <span>xp</span>, overwriting the original data at that location. Finally, the function returns the original value read in the first step. Although this function is short, it clearly demonstrates the dereference operation of pointers and the process of reading and writing memory, representing a fundamental “read-modify-write” pattern.
The Corresponding Assembly Code Implementation
The assembly code below accurately implements the functionality of the above C function. The comment at the beginning of the code <span>; xp in %rdi, y in %rsi</span> immediately clarifies the calling convention under the x86-64 architecture. This convention specifies that the first few integer or pointer parameters are passed through specific general-purpose registers. In this case, the first parameter (pointer <span>xp</span>) is placed in the <span>%rdi</span> register, and the second parameter (long integer value <span>y</span>) is placed in the <span>%rsi</span> register.
The function body consists of three instructions. The first instruction <span>movq (%rdi), %rax</span> performs the read operation. It retrieves a 64-bit value (quadword) from the address stored in the <span>%rdi</span> register and directly stores it in the <span>%rax</span> register. This cleverly utilizes the convention that the <span>%rax</span> register is typically used to hold the return value of a function, eliminating the need for intermediate register storage and directly preparing for the return. This instruction corresponds to the C code <span>long x = *xp;</span>.
Next, the second instruction <span>movq %rsi, (%rdi)</span> performs the write operation. It writes the value <span>y</span> stored in the <span>%rsi</span> register back to the memory address pointed to by the <span>%rdi</span> register. This corresponds to the C code <span>*xp = y;</span>, completing the memory update. Finally, the <span>ret</span> instruction is responsible for returning from the current function, at which point the return value is safely stored in the <span>%rax</span> register for the caller to use.
Key Insights and Summary
This simple example reveals several important concepts. First is efficiency; the translation of assembly code is not a word-for-word correspondence but is optimized, directly storing the read value into the return value register, enhancing execution efficiency. Second is the order of operations; the assembly instructions strictly follow the logical order of the C code: first reading the memory value, then updating the memory, and finally returning. This order is crucial; if disrupted, the function’s semantics would be completely incorrect.
Moreover, it vividly demonstrates the meaning of pointers at the machine level: a memory address stored in a register. Through the register indirect addressing mode (such as <span>(%rdi)</span>), the CPU can easily access and modify memory. Finally, it clarifies the rules of function calls, namely how parameters are passed (through specific registers) and how return values are returned (through <span>%rax</span>), which is fundamental for building larger software modules.
By conducting such a line-by-line comparative analysis, we can clearly see how abstract concepts in C language (such as pointers and function calls) are implemented at the machine level through specific registers and instructions. This understanding is a key step towards mastering system programming, performance optimization, and low-level debugging.