C#不安全代码内存错误偶现的原因及相关疑问
问题描述
我准备了一段代码片段来复现实际场景中的bug:
byte count = 100; byte* pointer = &count; (*(ushort*)pointer) = (ushort)byte.MaxValue;
我发现第三行存在错误,已将其修正为*pointer = byte.MaxValue;。但我无法理解为何错误代码有时能正常运行,有时却会抛出如下错误:
malloc.c:2379: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.
我意识到该指令会写入两个字节内存,第二个字节可能被当前进程其他变量或其他进程占用。我想知道:为何错误有时不会抛出?是否第二个字节空闲时就正常?或是仅当它属于其他进程地址空间时才报错?
问题解析
这本质是未定义行为的典型表现——C/C++标准里,越界访问内存的行为没有任何固定规则,完全取决于程序运行时的内存布局和被修改内存的用途。
为什么有时不报错?
你越界写入的第二个字节,如果刚好是进程地址空间里“无意义”的内存(比如栈上的编译器填充字节、未被使用的栈内存、或者某个暂时没在活跃使用的变量),写入操作不会立刻触发崩溃:要么这个内存的修改没影响到程序的关键逻辑,要么系统没检测到这个非法操作。比如栈上局部变量之间通常会有编译器自动添加的填充字节,写这些字节不会破坏任何有用数据,程序自然能正常运行。什么时候会崩溃?
当你写入的第二个字节属于内存管理结构的关键区域(比如malloc维护的堆元数据),或者是某个正在被使用的变量、甚至是栈帧的返回地址时,就会立刻触发断言错误或者崩溃。你看到的malloc.c断言失败,就是因为越界写入破坏了堆管理的内部数据结构,导致malloc后续的内存操作逻辑出错,触发了内置的断言检查。另外,要是越界的地址超出了当前进程的地址空间范围,系统会直接发送段错误(SIGSEGV)终止程序,但这种情况其实相对少见——因为栈和堆的内存都是连续分配的,越界的第一个字节大概率还在进程的合法地址范围内。
总结:不是“空闲”就正常,而是看被修改的内存是否是程序运行的关键数据。未定义行为没有规律,当下能跑不代表后续也能正常执行,必须从根源上杜绝越界操作。
内容的提问来源于stack exchange,提问作者vitm

