x86_64汇编与C交互:传递字符数组实现算术运算的方法
C调用x86_64 NASM汇编实现字符串数字加法
我来帮你搞定这个问题!你要实现的是C调用x86_64 NASM汇编函数处理字符串形式的数字算术运算,之前尝试直接遍历[rdi]和[rsi]存rax遇到困难,核心问题是没理清x86_64的调用约定,以及字符串数字的处理逻辑(毕竟char*是多字节的ASCII序列,不是单个数值)。下面我以字符串加法为例,给你完整的实现步骤和代码。
先搞懂x86_64 System V调用约定
在Linux/macOS的x86_64环境下,函数调用遵循System V规范:
- 第一个参数(
char* num1)存在rdi寄存器 - 第二个参数(
char* num2)存在rsi寄存器 - 函数返回值(
char*结果)存在rax寄存器 - 非易失寄存器(
rbx、rbp、r12-r15)如果被修改,必须先保存到栈中,用完再恢复 - 我们需要动态分配内存(调用
malloc)存储结果字符串,最终返回这个内存的指针
核心实现思路(以加法为例)
- 计算两个输入字符串的长度,确定结果的最大长度(最长输入长度+1,用来处理最高位进位)
- 从字符串末尾开始逐位遍历,把ASCII字符转成数值(减去
'0') - 对每一位的数值相加,加上上一位的进位,计算当前位的结果和新的进位
- 把当前位的数值转回ASCII(加上
'0')存入结果缓冲区 - 处理最后剩余的进位,给结果字符串加上结束符
- 返回结果缓冲区的指针
完整代码实现
1. C调用代码(main.c)
#include <stdio.h> #include <stdlib.h> // 声明汇编函数 extern char* str_add(char* num1, char* num2); int main() { char* num1 = "123456789"; char* num2 = "987654321"; char* result = str_add(num1, num2); printf("%s + %s = %s\n", num1, num2, result); // 必须释放汇编中malloc的内存,避免泄漏 free(result); return 0; }
2. NASM汇编代码(str_add.asm)
section .text global str_add extern malloc ; 声明C标准库的malloc函数 ; 辅助函数:计算字符串长度 ; 输入:rdi = 字符串指针 ; 输出:rax = 字符串长度 str_len: xor rax, rax ; 初始化长度计数器为0 .len_loop: cmp byte [rdi + rax], 0 ; 检查当前字符是否为结束符'\0' je .len_done ; 是结束符就退出循环 inc rax ; 计数器加1 jmp .len_loop .len_done: ret ; 主函数:字符串数字加法 ; 输入:rdi = num1指针, rsi = num2指针 ; 输出:rax = 结果字符串指针 str_add: ; 保存非易失寄存器到栈(后续要修改这些寄存器) push rbx push rcx push rdx push r8 push r9 push r10 push r11 ; 保存原始参数指针到非易失寄存器 mov rbx, rdi ; rbx = num1指针 mov rcx, rsi ; rcx = num2指针 ; 计算num1的长度 call str_len mov r8, rax ; r8 = num1长度 ; 计算num2的长度 mov rdi, rcx call str_len mov r9, rax ; r9 = num2长度 ; 确定结果的最大长度:max(len1, len2) + 1(处理进位) mov r10, r8 cmp r10, r9 jge .max_len_set mov r10, r9 .max_len_set: inc r10 ; r10 = 结果缓冲区长度 ; 调用malloc分配内存 mov rdi, r10 call malloc mov rsi, rax ; rsi = 结果缓冲区指针 ; 初始化循环变量 xor rdx, rdx ; rdx = 进位,初始为0 dec r8 ; r8 = num1最后一个字符的索引 dec r9 ; r9 = num2最后一个字符的索引 mov rdi, r10 ; rdi = 结果缓冲区当前写入索引(从末尾开始) dec rdi .add_loop: ; 获取num1当前位的数值 xor rax, rax cmp r8, -1 jl .num1_empty mov al, byte [rbx + r8] sub al, '0' ; ASCII转数值 jmp .num1_ready .num1_empty: mov al, 0 ; num1已遍历完,补0 .num1_ready: ; 获取num2当前位的数值 xor r11, r11 cmp r9, -1 jl .num2_empty mov r11b, byte [rcx + r9] sub r11b, '0' ; ASCII转数值 jmp .num2_ready .num2_empty: mov r11b, 0 ; num2已遍历完,补0 .num2_ready: ; 计算当前位总和:num1位 + num2位 + 进位 add al, r11b add al, dl mov dl, 0 ; 重置进位,准备计算新的进位 ; 处理进位:如果总和>=10,保留个位,进位设为1 cmp al, 10 jl .no_carry sub al, 10 mov dl, 1 .no_carry: ; 数值转ASCII,写入结果缓冲区 add al, '0' mov byte [rsi + rdi], al ; 移动索引,继续下一位 dec r8 dec r9 dec rdi ; 检查是否遍历完成:两个字符串都遍历完且无进位 cmp r8, -1 jne .add_loop cmp r9, -1 jne .add_loop cmp dl, 0 jne .add_final_carry ; 给结果字符串添加结束符 mov byte [rsi + r10 - 1], 0 jmp .done .add_final_carry: ; 还有剩余进位,添加到结果开头 mov byte [rsi], '1' mov byte [rsi + r10 - 1], 0 .done: ; 恢复栈中的非易失寄存器 pop r11 pop r10 pop r9 pop r8 pop rdx pop rcx pop rbx ; 返回结果缓冲区指针 mov rax, rsi ret
编译运行命令
在Linux环境下,执行以下命令编译运行:
# 编译汇编代码为64位目标文件 nasm -f elf64 str_add.asm -o str_add.o # 编译C代码并链接汇编目标文件 gcc main.c str_add.o -o str_add # 运行程序 ./str_add
运行后会输出:123456789 + 987654321 = 1111111110
关键注意事项
- 内存管理:汇编中调用
malloc分配的内存,必须在C代码中用free释放,否则会造成内存泄漏 - 寄存器保护:修改非易失寄存器前一定要保存到栈,函数返回前恢复,否则会破坏调用者的上下文
- 字符串边界:必须处理不同长度的输入字符串,短字符串遍历完后用0补位
- 进位处理:最高位相加后的进位一定要记得添加到结果中
- ASCII转换:数字字符和数值之间的转换要正确(加减
'0')
内容的提问来源于stack exchange,提问作者stos1512
相关产品推荐
相关产品推荐

