NASM x86汇编实现字符串去空格时出现段错误问题排查
你的段错误问题根源非常明确——你试图修改只读内存中的字符串字面量,我们一步步拆解问题并解决:
1. 段错误的直接原因
在你的C代码中:
char *a = "Remove spaces . ";
这里的"Remove spaces . "是字符串字面量,编译器会把这类常量放到程序的只读数据段(比如.rodata)中。而你的汇编代码里执行mov [eax], cl时,eax指向的就是这个只读内存的地址,操作系统会立即触发段错误来阻止非法的写操作。
你的逻辑是原地修改原字符串,但原字符串是只读的,这就导致了权限冲突。
2. 解决方案:让字符串可写
最简单的修复方式是修改C代码,把只读的字符串字面量换成可写的字符数组:
#include <stdio.h> #include <stdlib.h> extern char* remove_spaces(char *a); int main(){ char a[] = "Remove spaces . "; // 数组会把字符串复制到可写内存(栈/全局数据段) char *spaces_removed = remove_spaces(a); printf("\n%s", spaces_removed); return 0; }
这样字符串就存储在可写区域,汇编代码的写操作就合法了。
3. 汇编代码的其他问题修复
除了段错误,你的汇编代码还有三个关键问题需要修正:
3.1 缺少字符串结束符
处理完所有非空格字符后,你没有在结果末尾添加\0(字符串结束标记),这会导致后续printf读取到内存中的垃圾数据,直到随机遇到0字节。在rs_exit标签处添加一行:
rs_exit: mov [eax], byte 0 ; 添加字符串结束符
3.2 函数返回值错误
你的逻辑中,eax最终指向的是处理后字符串的末尾,而不是起始地址。需要在函数开头保存原字符串指针,最后恢复它作为返回值:
remove_spaces: push ebp mov ebp, esp mov eax, [esp+8] mov edx, eax push eax ; 把原字符串指针压栈保存 rs_loop: ; ... 原循环逻辑不变 ... rs_exit: mov [eax], byte 0 ; 添加结束符 pop eax ; 恢复原指针作为返回值
3.3 printf调用的栈平衡问题
调用printf后,你没有清理栈上的参数,会导致栈不平衡,引发后续错误。调用完printf后添加:
call printf add esp, 8 ; 清理printf的两个参数(每个4字节,共8字节)
另外,你的format2格式字符串看起来有拼写错误(DB "d* a=%d\ *d", 00ah),如果是想打印字符串,应该用%s格式符,比如改成:
format2: DB "处理后的字符串:%s", 00ah, 0
4. 完整修正后的汇编代码
section .text extern printf global remove_spaces remove_spaces: push ebp mov ebp, esp mov eax, [esp+8] mov edx, eax push eax ; 保存原字符串起始地址 rs_loop: mov cl, [edx] cmp cl, 0 je rs_exit inc edx cmp cl, ' ' je rs_loop mov [eax], cl inc eax jmp rs_loop rs_exit: mov [eax], byte 0 ; 添加字符串结束符 pop eax ; 恢复原指针作为返回值 ; 打印调试信息(可选) push eax push DWORD format2 call printf add esp, 8 ; 清理printf的参数 exit: mov esp, ebp pop ebp ret section .data format: DB "** a=%s **", 00ah, 0 format2: DB "处理后的字符串:%s", 00ah, 0 format3: DB "type is:%d ", 00ah, 0
可选方案:在汇编中分配新内存
如果你不想修改原字符串,也可以在汇编函数中调用malloc分配新内存,把处理后的字符串写入新内存,最后返回新指针。这种方式更安全,不会破坏原字符串:
section .text extern printf, malloc, strlen global remove_spaces remove_spaces: push ebp mov ebp, esp push ebx ; 保存ebx(调用者保存寄存器) mov eax, [esp+12] ; 原字符串指针 push eax call strlen ; 获取原字符串长度 add esp, 4 push eax ; 把长度作为malloc的参数 call malloc add esp, 4 mov ebx, eax ; ebx指向新分配的内存 mov edx, [esp+12] ; edx遍历原字符串 rs_loop: mov cl, [edx] cmp cl, 0 je rs_exit inc edx cmp cl, ' ' je rs_loop mov [ebx], cl inc ebx jmp rs_loop rs_exit: mov [ebx], byte 0 ; 添加结束符 mov eax, [esp+16] ; 返回新内存的指针 pop ebx mov esp, ebp pop ebp ret
这种方式需要链接时包含标准库(你的Makefile已经用gcc链接,没问题),且记得在C代码中free返回的指针避免内存泄漏。
内容的提问来源于stack exchange,提问作者vader

