我试图弄清楚C和C++如何在堆栈上存储大对象.通常,堆栈是整数的大小,所以我不明白在那里存储更大的对象.他们只是占用多个堆栈"插槽"吗?
确实,某些操作系统确实存在堆栈限制.(其中一些也有令人讨厌的堆限制!)
但这不再是1985年了.
这些天,我运行Linux!
我的默认stacksize限制为10 MB.我的默认堆大小是无限的.无限制堆栈大小是非常简单的.(*咳嗽*[tcsh] unlimit stacksize*cough*.或setrlimit().)
堆栈和堆之间的最大区别是:
堆栈分配只是偏移指针(如果堆栈已经变得足够大,可能会分配新的内存页面). Heap必须搜索其数据结构以找到合适的内存块.(也可能分配新的内存页面.)
当前块结束时,堆栈超出范围. 调用delete/free时,堆超出范围.
堆可能会碎片化. 堆栈永远不会碎片化.
在Linux下,堆栈和堆都通过虚拟内存管理.
就分配时间而言,即使通过严重碎片化的内存进行堆搜索也无法在新内存页面中进行映射. 时间差异可以忽略不计!
根据您的操作系统,通常只有当您实际使用它们映射到的新内存页时才会这样.(不是在malloc()分配期间!)(这是一个懒惰的评估事项.)
(new会调用构造函数,这可能会使用那些内存页......)
您可以通过在堆栈或堆上创建和销毁大对象来破坏VM系统.这取决于您的OS /编译器是否可以/由系统回收内存.如果它没有回收,堆可能能够重用它.(假设在此期间它没有被另一个malloc()重新调整用途.)同样,如果不回收堆栈,它只会被重用.
虽然换出的页面需要换回来,这将是你最大的时间点.
在所有这些事情中,我最担心内存碎片!
寿命(当它超出范围时)始终是决定因素.
但是当您长时间运行程序时,碎片会导致内存占用量逐渐增加.不断交换最终导致我死亡!
有些东西只是没有在这里加起来...我想或者*我*是地狱的基础.或者其他人都是.或者,两者都更有可能.或者,也许,也许.
无论答案是什么,我都必须知道发生了什么!
......这将是漫长的.跟我一起......
我过去12年的大部分时间都在Linux下工作.在各种版本的Unix下大约10年前.我对计算机的看法有些偏颇.我被宠坏了!
我在Windows上做了一点,但还不足以说话权威.不可悲的是,Mac OS/Darwin也不是......虽然Mac OS/Darwin/BSD足够接近,但我的一些知识仍然存在.
使用32位指针,您将耗尽4 GB(2 ^ 32)的地址空间.
实际上,STACK + HEAP组合通常限制在2-4 GB之间,因为其他东西需要在那里映射.
(共享内存,共享库,内存映射文件,运行总是很好的可执行映像等)
在Linux/Unix/MacOS/Darwin/BSD下,您可以人为地将HEAP或STACK约束到运行时所需的任意值.但最终存在硬系统限制.
这是"限制"与"限制-h"的区别(在tcsh中).或者(在bash中)"ulimit -Sa" vs "ulimit -Ha ".或者,以编程方式,在struct rlimit中使用rlim_cur vs rlim_max.
现在我们来到有趣的部分.关于马丁约克的守则.(谢谢Martin!很好的例子.总是好好尝试一下!)
大概是马丁在Mac上运行.(一个相当新的.他的编译器构建比我的更新!)
当然,他的代码默认不会在他的Mac上运行.但如果他首先调用"unlimit stacksize"(tcsh)或"ulimit -Ss unlimited"(bash),它就会运行得很好.
在一个古老的(过时的)Linux RH9 2.4.x内核框上进行测试,分配大量的STACK OR HEAP,它们自身最多可以在2到3 GB之间.(可悲的是,机器的RAM + SWAP最高不到3.5 GB.这是一个32位操作系统.这不是唯一运行的过程.我们用我们拥有的东西......)
所以Linux下的STACK大小与HEAP大小没有任何限制,除了人工的......
在Mac上,堆栈的硬限制为65532千字节.它与事物在记忆中的排列方式有关.
通常,您认为理想化系统在内存地址空间的一端具有STACK,在另一端具有HEAP,并且它们彼此构建.当他们相遇时,你就会失去记忆.
Mac似乎将它们的共享系统库固定在两侧之间的固定偏移处.您仍然可以使用"unlimit stacksize" 运行Martin York的代码,因为他只分配了8 MiB(<64 MiB)的数据. 但是在他用完HEAP之前很久他就会用完STACK.
我在Linux上.我不会. 对不起孩子.这是一个镍.去做一个更好的操作系统.
有Mac的解决方法.但它们变得丑陋和混乱,并且涉及调整内核或链接器参数.
从长远来看,除非Apple确实做了一些非常愚蠢的事情,否则64位地址空间将使整个堆栈限制的东西在Real Soon Now中被淘汰.
无论什么时候你把东西都推到了STACK上, 它就会附加到最后.并且只要当前块退出,它就被删除(回滚).
结果,堆叠中没有孔.它是所用内存的一大块固体.可能只是在最后一点点未使用的空间都可以重复使用.
相比之下,当HEAP被分配和释放时,你最终会使用未使用的内存空洞.随着时间的推移,这些可逐渐导致内存占用增加.不是我们通常所说的核心泄漏,但结果是相似的.
内存碎片不是避免HEAP存储的原因.这只是你编码时要注意的事情.
如果已经分配/使用了大量堆.
如果你周围散落着许多破碎的洞.
如果你有大量的小额分配.
然后你可以得到大量的变量,这些变量都在代码的一个小的本地化区域中使用,这些变量分散在很多虚拟内存页面中.(正如你在这个2k页面上使用4个字节,在2k页面上使用8个字节,依此类推很多页面......)
所有这些都意味着您的程序需要交换大量页面才能运行.或者它会不断地交换页面.(我们称之为捶打.)
另一方面,如果在STACK上进行了这些小的分配,它们将全部位于连续的内存中.需要加载较少的VM内存页面.(4 + 8 + ......胜利<2k.)
旁注:我之所以提醒注意这一点,是因为我知道某位电气工程师坚持认为所有阵列都是在HEAP上分配的.我们正在为图形做矩阵数学运算.A*LOT*为3或4个元素阵列.单独管理新/删除是一场噩梦.即使在课堂上抽象出来也会引起悲伤!
是的,默认情况下,线程仅限于非常小的堆栈.
您可以使用pthread_attr_setstacksize()更改它.虽然取决于您的线程实现,如果多个线程共享相同的32位地址空间,那么大的单个每线程堆栈将是一个问题! 没有那么多空间!再次,转换到64位地址空间(OS)将有所帮助.
pthread_t threadData; pthread_attr_t threadAttributes; pthread_attr_init( & threadAttributes ); ASSERT_IS( 0, pthread_attr_setdetachstate( & threadAttributes, PTHREAD_CREATE_DETACHED ) ); ASSERT_IS( 0, pthread_attr_setstacksize ( & threadAttributes, 128 * 1024 * 1024 ) ); ASSERT_IS( 0, pthread_create ( & threadData, & threadAttributes, & runthread, NULL ) );
也许你和我在想不同的事情?
当我想到一个堆栈帧时,我想到了一个调用堆栈.每个函数或方法都有自己的堆栈帧,包括返回地址,参数和本地数据.
我从未见过对堆栈帧大小的任何限制.作为一个整体,STACK存在局限性,但这就是所有堆栈帧的组合.
在Wiki上有一个很好的图表和堆栈帧的讨论.
在Linux/Unix/MacOS/Darwin/BSD下,可以通过编程方式更改最大STACK大小限制以及限制(tcsh)或ulimit(bash):
struct rlimit limits; limits.rlim_cur = RLIM_INFINITY; limits.rlim_max = RLIM_INFINITY; ASSERT_IS( 0, setrlimit( RLIMIT_STACK, & limits ) );
只是不要尝试在Mac上将其设置为INFINITY ...并在尝试使用它之前更改它.;-)
http://www.informit.com/content/images/0131453483/downloads/gorman_book.pdf
http://www.redhat.com/magazine/001nov04/features/vm/
http://dirac.org/linux/gdb/02a-Memory_Layout_And_The_Stack.php
http://people.redhat.com/alikins/system_tuning.html
http://pauillac.inria.fr/~xleroy/linuxthreads/faq.html
http://www.kegel.com/stackcheck/
堆栈是一块内存.堆栈指针指向顶部.值可以在堆栈上推送并弹出以检索它们.
例如,如果我们有一个用两个参数调用的函数(1个字节大小,另外2个字节大小;假设我们有一个8位PC).
两者都被推入堆栈,这会使堆栈指针向上移动:
03: par2 byte2 02: par2 byte1 01: par1
现在调用该函数并将返回的addres放在堆栈上:
05: ret byte2 04: ret byte1 03: par2 byte2 02: par2 byte1 01: par1
好的,在函数内我们有2个局部变量; 两个字节中的一个和4个中的一个.对于这些位置在堆栈上保留,但首先我们保存堆栈指针,以便通过向上计数来知道变量的起始位置,并通过倒计时找到参数.
11: var2 byte4 10: var2 byte3 09: var2 byte2 08: var2 byte1 07: var1 byte2 06: var1 byte1 --------- 05: ret byte2 04: ret byte1 03: par2 byte2 02: par2 byte1 01: par1
如您所见,只要剩余空间,您就可以在堆栈上放置任何东西.否则你会得到给这个网站起名字的现象.
Push
并且pop
指令通常不用于存储本地堆栈帧变量.在函数开始时,通过将堆栈指针递减函数的局部变量所需的字节数(与字大小对齐)来设置堆栈帧.这为这些值分配了"堆栈上"所需的空间量.然后通过指向此堆栈帧的指针(ebp
在x86上)访问所有局部变量.
堆栈是一个大的内存块,用于存储局部变量,从函数调用返回的信息等.堆栈的实际大小在操作系统上有很大差异.例如,在Windows上创建新线程时,默认大小为1 MB.
如果您尝试创建一个堆栈对象,该堆栈对象需要的内存大于堆栈上当前可用的内存,则会出现堆栈溢出并发生错误.大量的漏洞利用代码故意尝试创建这些或类似的条件.
堆栈不会划分为整数大小的块.它只是一个平坦的字节数组.它由size_t(非int)类型的"整数"索引.如果您创建一个适合当前可用空间的大型堆栈对象,它只是通过向上(或向下)堆栈指针来使用该空间.
正如其他人指出的那样,最好将堆用于大型对象,而不是堆栈.这可以避免堆栈溢出问题.
编辑:如果您正在使用64位应用程序并且您的操作系统和运行时库对您很好(请参阅mrree的帖子),那么在堆栈上分配大型临时对象应该没问题.如果您的应用程序是32位和/或您的OS /运行时库不是很好,您可能需要在堆上分配这些对象.