C语言内存乱码解析:未初始化数组越界访问相关疑问
嘿,咱们先明确下,你说的没错,这段代码确实没实际业务用途,但用来抠内存管理和操作系统行为的细节挺有意思的。先把你给出的代码贴这儿方便参考:
char some[]; for(int i = 0; ; i++) { printf("%c", some[i]); }
这段代码里的some数组未初始化,前9个字符输出相同的乱码,越界后还能持续输出,而且操作系统也没像你说的“理论上第11次循环就终止”,反而每次输出长度都随机——接下来咱们逐个拆解你的三个问题:
问题1:为何OS不在第11次迭代时终止程序,反而随机时长后才终止?
首先得纠正一个误解:理论上OS也不会严格在第11字节访问时就终止程序。Windows的内存保护是基于「内存页」的,一个标准内存页大小是4KB(4096字节)。当你的程序访问的地址还在当前进程的合法内存页范围内时,哪怕已经超出了some数组的边界,OS根本不会管——因为这些内存本来就属于你的进程。
那前9个字符为啥相同?因为未初始化的局部变量存在栈上,栈里的内容是之前程序运行留下的「垃圾数据」,刚好前9个字节的垃圾值一致而已。你越界访问的其实是栈里的其他数据:比如其他局部变量、函数的返回地址、栈帧的维护信息等等,这些都属于当前进程的合法内存,都在同一个内存页里,所以OS不会触发错误。
只有当你访问的地址超出了当前进程的虚拟内存空间范围,或者踩到了被OS标记为「不可访问」的内存页时,才会触发「访问违规(Access Violation)」,这时OS才会终止你的程序。而每次运行程序时,栈的起始位置、当前进程的内存布局可能因为进程加载基址、其他运行时数据的不同而变化,所以触发终止的时机自然是随机的。
问题2:为什么有些字符显示为空白方块,而不是ASCII扩展集的字符?
这锅得甩给终端(或者VS的输出窗口)的编码和字体支持,和C语言的char类型没关系——char确实是1字节,但输出的字符能不能正常显示,取决于终端能不能识别这个字节对应的编码,以及字体有没有对应的字形。
举个例子:Windows控制台默认可能用GBK或者UTF-8编码(看系统设置),那些空白方块对应的字节,要么是ASCII里0-31的「不可打印控制字符」(比如换行、退格、响铃这些,有些会触发终端行为,有些没对应显示符号就变成方块),要么是当前终端编码里没有映射对应的字符,或者字体不支持该字符的渲染。
至于ASCII扩展集(128-255)的字符,不同编码标准里对应的符号不一样——比如在ISO-8859-1里的扩展字符,和GBK里的完全不同,如果终端用的编码和你输出的字节不匹配,或者字体没有这个符号的字形,自然就显示成空白方块了。
问题3:是否可以认为这些被访问的字节正被其他程序占用?
绝对不行。Windows是多进程操作系统,每个进程都有自己独立的「虚拟地址空间」——也就是说,你程序里访问的地址都是虚拟地址,不是物理内存的真实地址。你越界访问的所有字节,都是属于当前进程自己的虚拟内存空间里的内容:可能是栈上的其他垃圾数据、堆里的未使用区域,或者是进程加载的代码段、只读数据段的边缘,但绝对不会是其他程序的内存。
Windows的内存管理会把不同进程的虚拟地址映射到不同的物理内存页,还有严格的隔离机制,一个进程根本无法直接访问另一个进程的内存(除非用共享内存、进程注入这类特殊的进程间通信手段,你这段代码显然没用到)。所以你访问的都是自己进程的垃圾数据,和其他程序半毛钱关系都没有。
内容的提问来源于stack exchange,提问作者Victor Sysoev

