栈的输出_深入浅出堆、栈、进程、线程

把qq空间里面一篇,也是自己写的唯一一篇技术文章转到知乎,好多年前写的了,难免错误

3fde5fd8a3e4e8bc30e1bb9e0713a728.png

这是我写的第一篇技术文章,我觉得有必要写,前天在研究周克华,昨天晚上在查关于线程、进程堆栈的文章把我直接都给搞晕了,好多概念都是错误的。连《Windows内核编程》这本书,公认大牛级著作,但是译者在翻译堆栈那块的时候,也是狗屎一坨。甚至把堆都给译成了堆栈,网上都说栈译成堆栈,堆就是堆,栈就是栈。不知道是那个傻×最先把栈译成堆栈的。这个问题网上都讲的是模棱两可,那大学教科书就更是狗屁不通了,所以搞了这么久我也一直不能很好的理解这个概念,今天上班不甘心,然后自己动手再写代码测试,突然..突然….一下就开窍了。 我觉得作为一个程序员,他对堆、栈、线程、进程有几成理解,他就有几成水平。如果你认为你很牛叉了你试着回答这几个问题。堆和栈到底有什么区别?堆和栈地址空间是多大?进程默认栈空间是多大?线程默认栈空间、堆空间是多大? 进程跟线程到底有什么区别?内存到底是如何分配的?线程的栈空间是进程给的吗? 好的进入正题,我们先看一张图

520be462cfecdb2b598edec65b27b4b0.png

21b2da276c1dd4d0fbe08e5d16097160.png

上图是任务管理器截的图,最上面是CPU使用情况,下面是什么使用情况?有人说是内存 没错是内存,但是他是什么内存?是物理内存吗?上面写着是页面文件,代表不是物理内存,作为一个码农,时刻要保持怀疑精神和刨根问底的精神,这页面文件到底是个神马玩意呢? 是虚拟内存,对,你没听错,就是虚拟内存,是你硬盘的虚拟内存空间,曾经内存很少的时候,假设你只有4M的内存,你的进程最多就只可以运行一个4M的程序,后来人越来越聪明感觉这不行啊,老子得想个办法,要不岛国片子都没法看,后来想出了映射的方法,虚拟一个4G的地址空间,为什么是4G,因为一个指针的寻址范围是4G,然后需要用时,再把页文件装载到物理内存,一个页面大小是4K,Windows的分配粒度是64K,这是不是代表有4G的地址可以用啊?这里面又分为用户区,内核区、指针区,我们可以用的只有用户区,2G不到,我在自己本机上测试的。 我们申请的内存都是虚拟内存,要提交给物理内存才能够访问,当使用virtualAlloc的时候先使用reserve 保留,然后再使用commit 提交。使用new 分配的时候,系统会自动帮我们提交给物理内存。由于是介绍线程、进程、堆栈。所以这个就不详细了。但是必须先理解清楚这个。 二 用户区他有堆区、栈区、全局区、静态区,不同的方式定义变量,会分配在不同的区域。 Int a; (分配在栈区) int *a = new int(); (分配在堆区) char *p= "123456"; (1234560在常量区,p在栈上)。栈是向下生长的,堆是向下生长的我们待会用实例来证明,堆分配是动态的,一定要自己释放,否则后果不堪设想,使用了new 必须成对使用 delete 定义 int i = 1;int j = 1; int k = 1; cout <<&i<<endl;cout<<&j<<endl; cout<<&k<<endl; 我的本机会输出

dcf76ba67a4457d6bd38f49c8f3b40be.png

21b2da276c1dd4d0fbe08e5d16097160.png

地址是递减的所以证明了栈他是向下增长的。 定义 int *i = newint(); int j1 = new int(); int k1 = new int(); 我的本机会输出

42c4f8f3829da5d01b63c22551c9d36c.png

21b2da276c1dd4d0fbe08e5d16097160.png

地址是递增的证明堆的空间是向上增长的。 再来看进程这个概念,创建一个进程他会包含两个部分 一:地址空间区域 (每个进程理论都会有我们上面说的那4G地址空间,但是实际可用不到2G)二:管理进程的内核对象 (这个由系统自动创建)进程他本不是CPU最小调度单位,只是把可执行文件或者DLL文件装载到进程地址空间来,格式是PE格式。包括代码区、数据区、文本区、然后由线程执行,线程才是CPU的调度单位,每个进程至少都有一个线程,那就是主线程,如果没有一个线程,那进程也没有存在的必要了,那系统就会撤销该进程,进程内核对象就是管理引用计数的。文字太繁琐了,直接是代码,上运行结果更容易理解。 一个进程可以创建多少个线程呢? 我查了MSDN说不要超过32个 Window内核编程说不超过64个 网上有人说最好是CPU核数*2+2 我按照太宗皇帝的指示“实践是检验真理的唯一标准” 经过不不懈的实践得出,只要主线程等待线程返回后再创建,理论上可以创建无数个线程,但是同时并发我就没测试了,因为线程返回后他的地址空间也就撤销了,

dfd630dd3f2d5ef55d30b26e2bdf337e.png

21b2da276c1dd4d0fbe08e5d16097160.png

一直可以到N,但是如果你线程还没有退出,主线程又来创建改线程,但地址范围快到2G的时候就会产生溢出错误, 线程的默认栈空间是多大呢? 查MSDN说是1M 其实没有1M将近1M的样子,在主线程中定义char a[1024*1024](知道这个为什么会是一兆不? ); 会出现溢出错误。

7ef9832a3aa795916fca58de0f511df1.png

21b2da276c1dd4d0fbe08e5d16097160.png

但是如果在主线程定义char a[1024* 800] 不会报错 使用代表默认栈空间是将近1M的样子,这个可以修改 通过/Stack: 项目—属性—链接器—系统 修改 线程默认堆空间是多少呢,MSDN上也是说1M ,但是我可以在主线程中开辟一个1G的堆空间都没问题,但是当快到底2G时候就出产生溢出错误 在主线程中定义 char*a = new char[1024*1024*1024]; 不会错误 ,查看任务管理器可以看出

a5186d49012b7034f0531a35049580fa.png

21b2da276c1dd4d0fbe08e5d16097160.png

但是如果在主线程中定义char *a = new char[1024*1024*1024*2] 就会出现溢出错误 errorC2148: total size of array must not exceed 0x7fffffff bytes 使用LPVOID p = VirtualAlloc(NULL,1024*1024*1024,MEM_RESERVE |MEM_COMMIT,PAGE_READWRITE); boolflag=VirtualFree(p,1024*1024*1024,MEM_DECOMMIT); if(flag) printf("Okn");也是得出一样的结果 由于堆是动态分配的,不可以很好的验证他的默认大小是不是1M 但是最多可以分配的堆不可以超过2G是可以验证了的。 还有一个方法可以验证堆大小默认堆是1M 当使用主线程再创建32个线程时候 代码如 DWORD_stdcall ThreadFun(PVOID p) { DOWRD a; Printf(“%d:%xn”,p,&a); } 会输出

11573705e1b264df8b7517cc337d6b6f.png

21b2da276c1dd4d0fbe08e5d16097160.png

61ffac – 51ffac = 10000 证明地址空间也是1M 不过说了默认堆、栈大小是可以调整的。 好的现在来总结下: 进程默认的栈大小是1M(其实就是主线程的堆、栈) 默认堆也是1M(没验证) 线程的默认栈大小也是1M 他们都是拥有自己的栈空间,不要认为进程可以有很多线程,然后进程的栈空间就是所有线程栈空间的总和,是进程分配给他们的,这是个非常荒唐的想法,昨天也困扰着我。堆的话最多不可以超过2G 进程线程都是一样。或者干脆说线程才有堆空间、栈空间这个概念,进程没有,进程有他也是主线程的。他只是一个地址空间区域跟一个进程内核对象。 好的就写到这里,这文章仅代表我个人的观点,不保证里面观点全部正确,时间仓促而且今天状元楼还没饭吃,吃的泡面,所以之中可能难免会有些错误,仅供参考。总之一条码键盘的一定不能打马虎,错综复杂的抽象概念,自己一定要动手去检验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值