小实验:PUSH到栈但不POP是否会引发错误或泄漏

    一个有趣的小实验,它将告诉我们PUSH到栈但不POP会不会发生错误或栈泄漏,本文只附一张运行结果图简单说说,剩下就靠各位自行试验领悟。

    看上去似乎很有趣的~是吧,其实这么一个小测试你就想明白很多东西,例:栈的工作行为,任何程序都具有相应的工作栈【计算堆栈】,纵然是 “基于寄存器式过程虚拟机” 也是相同的,但它仅仅只是结合寄存器的作用,提高代码的效能减少对栈内存的 R/D,但它仅仅只是一种优化手段,例如 C/C++ complier、.NET CLR/JIT 都采取这种手段。

    栈之间POP的操作并不是真的清理栈内存,所以不会存在“栈泄漏”这种伪概念【有人作妖要弄这个超前的概念出来的话,我就不说啥了,你厉害!】,要是强行的定性为泄漏,那么栈内存就等于一直处在泄漏的状态,而栈溢出是历史悠久就存在的问题,POP 它只不过是 “mov XX,esp, add esp,sizeof(XX)” 指令序列的一种简化,并不是那么的神奇也不是那么奇妙。

    上面说栈没有“栈泄漏”这种伪概念,但是引发异常是肯定的,但是为什么下述代码执行以后没有引发错误?我们知道 C/C++ 语言编写一个函数每个都有一个大 “{ }” 花括号包住,而这个花括号的 body 部分才是我们自己编写的用户代码,那么这个花括号真的什么都没有干?显然不是它的作用或许大到令人“口【可】怕”,它在用户代码执行之前处理了栈与一些寄存器值一些的保存与重置,在用户代码之后清理平衡堆栈【真实情况是没有清理的,栈内存不需要清理或重置内存】同时栈上分配的 “结构对象” 具有 “终结器-析构函数” 则调用(但这个是由编译器实现的,与栈之间不相干)同时恢复原来寄存器的值,ESP寄存器叫做 “基顶指针寄存器” 或者说就是当前栈的R/D位置,但是ESP的值并不是由POP恢复的,当前C/C++函数体内的EBP指令其实就等于曾经的ESP指针,你只需要弄明白函数的计算堆栈是如何进行工作的就能轻易想明白(所以当年的JMP ESP大发才那么疯狂,就是利用的这种特性)。

   曾经有人说 .NET CLR 与 Java JVM 是一个 “基于栈式过程虚拟机” 似乎这句话并没有什么问题,MSIL与Java Bcode指令的确是基于栈式计算的,但 .NET CLR/JIT 编译后的 native-code 却不全是栈式的。过渡吹嘘某个东西用来贬低别人其实你这个人真的会有点让人感到不尽的可耻。

#pragma once

#include <stdio.h>

void test()
{
    char* srcesp;
    _asm
    {
        mov srcesp, esp
    }
    for (int i = 0; i < 1000; i++)
    {
        _asm
        {
            push i;
        }
    }
    char* curesp;
    _asm
    {
        mov curesp, esp
    }
    printf("se=%d|ce=%d|df=%d", srcesp, curesp, (srcesp - curesp));
}

int main(int argc, char* argv[])
{
    test();
    return getchar();
}

猜你喜欢

转载自blog.csdn.net/liulilittle/article/details/82348421
今日推荐