none
全面介绍Windows内存管理机制及C++内存分配实例 之 堆栈 RRS feed

  • 常规讨论

  • 本文背景:

    在编程中,很多WindowsC++的内存函数不知道有什么区别,更别谈有效使用;根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制。

    本文目的:

    Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。
    1.   进程地址空间
    2.      内存状态查询函数
    3.     内存管理机制--虚拟内存 (VM)

    5.      内存管理机制-- (Heap)

    ·        使用场合

    操作系统为每个线程都建立一个默认堆栈,大小为1M。这个堆栈是供函数调用时使用,线程内函数里的各种静态变量都是从这个默认堆栈里分配的。

    ·        堆栈结构

    默认1M的线程堆栈空间的结构举例如下,其中,基地址为0x0004 0000,刚开始时,CPU的堆栈指针寄存器保存的是栈顶的第一个页面地址0x0013 F000。第二页面为保护页面。这两页是已经分配物理存储器的可用页面。

    随着函数的调用,系统将需要更多的页面,假设需要另外5页,则给这5页提交内存,删除原来页面的保护页面属性,最后一页赋予保护页面属性。

    当分配倒数第二页0x0004 1000时,系统不再将保护属性赋予它,相反,它会产生堆栈溢出异常STATUS_STACK_OVERFLOW,如果程序没有处理它,则线程将退出。最后一页始终处于保留状态,也就是说可用堆栈数是没有1M的,之所以不用,是防止线程破坏栈底下面的内存(通过违规访问异常达到目的)。

    当程序的函数里分配了临时变量时,编译器把堆栈指针递减相应的页数目,堆栈指针始终都是一个页面的整数倍。所以,当编译器发现堆栈指针位于保护页面之下时,会插入堆栈检查函数,改变堆栈指针及保护页面。这样,当程序运行时,就会分配物理内存,而不会出现访问违规。

    4.      内存管理机制--内存映射文件 (Map)


    Smile service,common progress!
    2009年5月23日 16:02

全部回复

  • ·        使用例子

    改变堆栈默认大小:

    有两个方法,一是在CreateThread()时传一个参数进去改变;

    二是通过链接命令:

    #pragma comment(linker,"/STACK:102400000,1024000")

    第一个值是堆栈的保留空间,第二个值是堆栈开始时提交的物理内存大小。本文将堆栈改变为100M

             堆栈溢出处理:

            如果出现堆栈异常不处理,则导致线程终止;如果你只做了一般处理,内

            结构已经处于破坏状态,因为已经没有保护页面,系统没有办法再抛出堆栈溢

            出异常,这样的话,当再次出现溢出时,会出现访问违规操作

            STATUS_ACCESS_VIOLATION,这是线程将被系统终止。解决办法是,恢复

           堆栈的保护页面。请看以下例子:

           C++程序如下:

    bool handle=true;

                static MEMORY_BASIC_INFORMATION mi;

                LPBYTE lpPage;

                //得到堆栈指针寄存器里的值

                _asm mov lpPage, esp;

                // 得到当前堆栈的一些信息

                VirtualQuery(lpPage, &mi, sizeof(mi));

                //输出堆栈指针

                printf("堆栈指针=%x\n",lpPage);

                // 这里是堆栈的提交大小

                printf("已用堆栈大小=%d\n",mi.RegionSize);

                printf("堆栈基址=%x\n",mi.AllocationBase);

                                       

                for(int i=0;i<2;i++)

                {

                            __try

                            {

                                        __try

                                        {

                                                    __try

                                                    {

                                                                cout<<"**************************"<<endl;

                            //如果是这样静态分配导致的堆栈异常,系统默认不抛出异常,捕获不到

                                                                //char a[1024*1024];

                                                    //动态分配栈空间,有系统调用Alloca实现,自动释放

                                                                Add(1000);

                                                                //系统可以捕获违规访问

                                                                int * p=(int*)0xC00000000;

                                                                *p=3;

                                                                cout<<"执行结束"<<endl;

                                                    }

                                                    __except(GetExceptionCode()==STATUS_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)

                                                    {

                                                                cout<<"Excpetion 1"<<endl;

                                                    }

                                        }

                                        __except(GetExceptionCode()==STATUS_STACK_OVERFLOW ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)

                                        {

                                                    cout<<"Exception 2"<<endl;

                                                    if(handle)

                                                    {

                                                    //做堆栈破坏状态恢复

                                                                LPBYTE lpPage;

                                                                static SYSTEM_INFO si;

                                                                static MEMORY_BASIC_INFORMATION mi;

                                                                static DWORD dwOldProtect;

     

                                                                // 得到内存属性

                                                                GetSystemInfo(&si);

     

                                                                // 得到堆栈指针

                                                                _asm mov lpPage, esp;

                                                                // 查询堆栈信息

                                                                VirtualQuery(lpPage, &mi, sizeof(mi));

                                                                printf("坏堆栈指针=%x\n",lpPage);

                                                                // 得到堆栈指针对应的下一页基址

    lpPage = (LPBYTE)(mi.BaseAddress)-si.dwPageSize;

                                                                printf("已用堆栈大小=%d\n",mi.RegionSize);

                                                                printf("坏堆栈基址=%x\n",mi.AllocationBase);

                                                                //释放准保护页面的下面所有内存

                                                                if (!VirtualFree(mi.AllocationBase,

    (LPBYTE)lpPage - (LPBYTE)mi.AllocationBase,

                                                                            MEM_DECOMMIT))

                                                                {         

                                                                            exit(1);

                                                                }

                                                                // 改页面为保护页面

                                                                if (!VirtualProtect(lpPage, si.dwPageSize,

                                                                            PAGE_GUARD | PAGE_READWRITE,

                                                                            &dwOldProtect))

                                                                {

                                                                            exit(1);

                                                                }

                                                    }

                                                    printf("Exception handler %lX\n", _exception_code());

                                        }

                            }

                            __except(EXCEPTION_EXECUTE_HANDLER)

                            {

                                        cout<<"Default handler"<<endl;

                            }

                }

               

                cout<<"正常执行"<<endl;

                //分配空间,耗用堆栈

                char c[1024*800];

                printf("c[0]=%x\n",c);

                printf("c[1024*800]=%x\n",&c[1024*800-1]);

    }

     

    void ThreadStack::Add(unsigned long a)

    {

                //深递归,耗堆栈

                char b[1000];

                if(a==0)

                return;

                Add(a-1);

     

    }

     

    程序运行结果如下:

    可以看见,在执行递归前,堆栈已被用了800K,这些是在编译时就静态决定了。它们不再占用进程空间,因为堆栈占用了默认的1M进程空间。分配是从栈顶到栈底的顺序。

    当第一次递归调用后,系统捕获到了它的溢出异常,然后堆栈指针自动恢复到原来的指针值,并且在异常处理里,更改了保护页面,确保第二次递归调用时不会出现访问违规而退出线程,但是,它仍然会导致堆栈溢出,需要动态的增加堆栈大小,本文没有对这个进行研究,但是试图通过分配另外内存区,改变堆栈指针,但是没有奏效。

    注意:在一个线程里,全局变量加上任何一个函数里的临时变量,如果超过堆栈大小,当调用这个函数时,都会出现堆栈溢出,这种溢出系统不会抛出堆栈溢出异常,而直接导致线程退出。

    对于函数1调用函数2,而函数n-1又调用函数n的嵌套调用,每层调用不算临时变量将损失240字节,所以默认线程最多有1024*(1024-2)/240=4360次调用。加上函数本身有变量,这个数目会大大减少。(完)


    Smile service,common progress!
    2009年5月23日 16:03