在C 语言中,长度大于1 的字符串怎么会被放进malloc(1) 分配的内存里?
请看这个,
{
char* p = malloc(1);
strcpy(p, "Hello World!");
printf("%s", p);
}
这将输出 Hello World!。
不过,这到底是怎么回事?我本以为这会失败,结果似乎还是成功了。我是不是漏看了什么?
解决方案
这类情况在初学者学习C 时经常会被问到。它们做错了某些事,却期望得到像错误信息那样确定的结果。但C 只有在你把事情做对时才是确定性的。当你做错事并触发 未定义行为,那么任何事情都可能发生——包括程序现在看起来似乎还能正常运行。
编译器只有在你的代码违反语法或核心语言规则时才有义务给出提示——约束,在其他情况下它并不一定会提示。大多数未定义行为都是运行时问题,因此编译器甚至可能检测不到它们。
为什么这次它会工作?
- 这次你写入的区域恰好有一些堆内存没有被占用,因此这次它起作用了。
- 也有可能你写入的区域里曾经包含对程序非常重要的堆内存,现在你已经把这段内存搞坏了,但至少你现在还能正确打印 "hello world"。
- 也有可能编译器把整段代码优化掉,把你的整个程序替换成等价于
puts("hello world")的形式。 - 或者其他原因。
更多细节请看:未定义行为是什么以及它是如何工作的?
至于如何避免——如果你使用的是较新版本的gcc,它内置了一个通过 -fsanitize 调用的 动态分析器。用 -fsanitize=address 编译你的代码时,会提供关于发现的堆缓冲区溢出的额外信息,如下所示:
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x745b11de0011 at pc 0x783b1343eb44 bp 0x7ffc98f9f760 sp 0x7ffc98f9ef20
WRITE of size 13 at 0x745b11de0011 thread T0
#0 0x783b1343eb43 in memcpy (/opt/compiler-explorer/gcc-16.1.0/lib64/libasan.so.8+0x129b43) (BuildId: c1ce38ce5c88b82634d528001926b27404a36d26)
#1 0x0000004010cf in main /app/example.c:8
#2 0x783b1302a1c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#3 0x783b1302a28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#4 0x000000401144 in _start (/app/output.s+0x401144) (BuildId: e88056f2bbba54c59020b83edfbea86660ac42d5)
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。