为何GCC会将存在错误的C代码编译为该特定汇编指令?
原始C代码
#include<stdio.h> #include<stdlib.h> int main(int argc, char** argv) { char *str5=malloc(10); *str5="xxxxx\0"; printf("%s\n",str5); return 0; }
编译后生成的汇编代码
使用命令 gcc source.c 编译后,得到如下汇编指令:
=> 0x555555555179 <main+4>: sub rsp,0x20 0x55555555517d <main+8>: mov DWORD PTR [rbp-0x14],edi 0x555555555180 <main+11>: mov QWORD PTR [rbp-0x20],rsi 0x555555555184 <main+15>: mov edi,0xa 0x555555555189 <main+20>: call 0x555555555040 <malloc@plt> 0x55555555518e <main+25>: mov QWORD PTR [rbp-0x8],rax 0x555555555192 <main+29>: lea rax,[rip+0xe6b] # 0x555555556004 0x555555555199 <main+36>: mov edx,eax 0x55555555519b <main+38>: mov rax,QWORD PTR [rbp-0x8] 0x55555555519f <main+42>: mov BYTE PTR [rax],dl 0x5555555551a1 <main+44>: mov rax,QWORD PTR [rbp-0x8] 0x5555555551a5 <main+48>: mov rdi,rax 0x5555555551a8 <main+51>: call 0x555555555030 <puts@plt> 0x5555555551ad <main+56>: mov eax,0x0 0x5555555551b2 <main+61>: leave 0x5555555551b3 <main+62>: ret 0x5555555551b4 <_fini>: sub rsp,0x8 0x5555555551b8 <_fini+4>: add rsp,0x8 0x5555555551bc <_fini+8>: ret
用户疑问
说明:我不是问怎么正确复制或赋值字符串——我知道正确做法。我想搞懂的是,为什么GCC会把这段代码编译成这样的指令集。
实际执行情况:
执行到main+42处时,代码把字符串常量"xxxxx\0"地址的最低字节(dl寄存器的值)写入rax指向的内存(也就是str5指向的堆内存),最终只会得到一个无效字符,根本没复制字符串常量的其他部分。编译器给出了如下警告,但我搞不懂和这个问题的关联:source.c:6:14: warning: assignment to ‘char’ from ‘char *’ makes integer from pointer without a cast [-Wint-conversion]预期执行情况:
字符串常量"xxxxx\0"被写入str5指向的内存,然后这个字符串被打印到屏幕上。
核心问题:为什么GCC会这么编译这段代码?
解答
1. 代码语义的本质错误
你写的*str5="xxxxx\0";和你预期的“复制字符串”完全不是一回事:
str5是char*类型,*str5表示的是str5指向的第一个字节(单个char类型的字符)。"xxxxx\0"作为字符串常量,在C语言中会被自动转换为指向该字符串首地址的char*指针。- 这条语句的实际语义是:把一个指针(地址值)赋值给单个
char变量。由于char仅占1字节,编译器只能取指针地址的最低1字节写入*str5指向的内存——这就是汇编指令mov BYTE PTR [rax],dl的作用:dl是edx的低8位,而edx存储的正是字符串常量的地址值。
2. 编译器警告的直接关联
那条警告已经明确点出问题:assignment to ‘char’ from ‘char *’ makes integer from pointer without a cast,意思是你在把char*类型的指针赋值给char类型变量,相当于把指针(整数类型)强制转换成了1字节的char,且没有显式类型转换。这完全对应代码里的错误操作,也直接解释了汇编指令的生成逻辑。
3. 编译器的严格语义遵循
C语言没有“直接赋值字符串”的语法,字符串常量本身是指针,不是可直接赋值的“字符串值”。编译器只会严格按照你写的代码语义生成指令——你写的是给单个字符赋值,它就只会生成单个字符赋值的汇编,不会自动帮你做字符串复制的操作。
内容的提问来源于stack exchange,提问作者skiwarz

