我试图弄清楚 C 和 C++ 如何在堆栈上存储大对象。通常,堆栈的大小是整数,所以我不明白更大的对象是如何存储在那里的。它们只是占用多个堆栈“槽”吗?

有帮助吗?

解决方案

堆栈是一块内存。堆栈指针指向顶部。值可以在堆栈上推送并弹出以检索它们。

例如,如果我们有一个用两个参数调用的函数(1个字节大小,另外2个字节大小;假设我们有一个8位PC)。

两者都被推入堆栈,这会使堆栈指针向上移动:

03: par2 byte2
02: par2 byte1
01: par1

现在调用该函数并将返回的地址放在堆栈上:

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

如您所见,只要剩余空间,您就可以在堆叠上放置任何东西。否则你会得到给这个网站起名字的现象。

其他提示


堆栈和堆没有您想象的那么不同!


确实,某些操作系统确实存在堆栈限制。 (其中一些也有令人讨厌的堆限制!)

但这不再是1985年了。

这些天,我运行Linux!

我的默认 stacksize 限制为10 MB。我的默认 heapsize 是无限制的。无限制堆栈大小是非常简单的。 (*咳嗽* [tcsh] unlimit stacksize * cough *。或 setrlimit()。)

堆栈之间的最大区别是:

  1. 堆栈分配只是偏移了一个指针(如果堆栈增长得足够大,可能会分配新的内存页)。 必须搜索其数据结构以找到合适的内存块。 (也可能分配新的内存页面。)
  2. 当前块结束时,
  3. 堆栈超出范围。调用delete / free时,超出范围。
  4. 可能会碎片化。 堆栈永远不会碎片化。
  5. 在Linux下, stack heap 都通过虚拟内存管理。

    就分配时间而言,即使通过严重碎片化的内存进行堆搜索也无法在新内存页面中进行映射。 时间差异可以忽略不计!

    取决于您的操作系统,通常只有在您实际使用它们映射到的新内存页时才会这样。( malloc()分配期间 NOT !)(这是懒惰评估的事情。)

    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 约束为运行时所需的任意值。但最终存在硬系统限制。

    这是" limit" vs " limit -h" 的区别(在tcsh中)。或者(在bash中)" ulimit -Sa" vs " ulimit -Ha" 。或者,以编程方式, struct  rlimit 中的 rlim_cur vs rlim_max


    现在我们来到有趣的部分。关于 Martin York的代码 。 (谢谢 Martin !很好的例子。总是好好尝试一下!)

    Martin的大概是在Mac上运行。 (最近的一个。他的编译器构建比我的更新!)

    当然,他的代码默认不会在他的Mac上运行。但如果他首先调用“unlimit stacksize”(tcsh)或“ulimit -Ss unlimited”(bash),它就会运行得很好。


    重要的心脏:


    在古老(过时的)Linux RH9 2.4.x内核框上进行测试,分配大量的 STACK     HEAP ,其中一个本身最高可达2到3 GB之间。 (可悲的是,该机器的RAM + SWAP最高不到3.5 GB。它是一个32位操作系统。这是 NOT 唯一正在运行的进程。我们用我们拥有的东西做... )

    所以Linux下的 STACK 大小与 HEAP 大小没有任何限制,除了人为的......


    BUT:


    在Mac上,堆栈大小限制为 65532千字节。这与事物在记忆中的布局有关。


    通常,您认为理想化的系统具有 STACK 位于内存地址空间的一端, HEAP 位于另一端,它们相互构建。当他们相遇时,你就会失去记忆。

    Mac似乎在限制双方的固定偏移量之间粘贴了共享系统库。您仍然可以使用“unlimit stacksize”运行 Martin York的代码 ,因为他只分配了8 MiB(<64 MiB)的数据。 但是在用完 HEAP 之前很久他就会耗尽 STACK

    我在Linux上。我不会。 对不起孩子。这是一个镍。为自己创造一个更好的操作系统。

    Mac有解决方法。但它们变得丑陋和混乱,并涉及调整内核或链接器参数。

    从长远来看,除非Apple确实做了一些非常愚蠢的事情,否则64位地址空间将使整个堆栈限制的东西在Real Soon Now中过时。


    继续碎片化:


    任何时候你把东西推到 STACK &nbsp;它附加到最后。并且只要当前块退出,它就被删除(回滚)。

    因此, STACK 中没有漏洞。它是所用内存的一大块固体。可能只有一小部分未使用的空间都可以重复使用。

    相比之下,当 HEAP 被分配并免费时,您将使用未使用的内存孔。随着时间的推移,这些可逐渐导致内存占用增加。不是我们通常所说的核心泄漏,但结果是相似的。

    内存碎片 NOT 是避免 HEAP 存储的理由。在编码时,这只是需要注意的事项。


    w ^

推送 pop 指令通常不用于存储本地堆栈帧变量。在函数开始时,通过将堆栈指针递减函数的局部变量所需的字节数(与字大小对齐)来设置堆栈帧。这在“堆栈”上分配必要数量的空间。对于这些价值观。然后通过指向此堆栈帧的指针访问所有局部变量(x86上的 ebp )。

堆栈是一个大的内存块,用于存储局部变量,从函数调用返回的信息等。堆栈的实际大小在操作系统上有很大差异。例如,在Windows上创建新线程时,默认大小为1 MB

如果您尝试创建一个堆栈对象,该堆栈对象需要的内存大于堆栈上当前可用的内存,则会出现堆栈溢出并发生错误。一大类漏洞利用代码故意试图创建这些或类似的条件。

堆栈不会划分为整数大小的块。它只是一个平坦的字节数组。它由“整数”索引。类型size_t(不是int)。如果您创建一个适合当前可用空间的大型堆栈对象,它只是通过向上(或向下)堆栈指针来使用该空间。

正如其他人所指出的,最好将堆用于大对象,而不是堆栈。这可以避免堆栈溢出问题。

编辑:如果您正在使用64位应用程序并且您的操作系统和运行时库对您很好(请参阅mrree的帖子),那么在上面分配大型临时对象应该没问题。堆。如果你的应用程序是32位和/或你的操作系统/运行时库不好,你可能需要在堆上分配这些对象。

无论何时输入函数,堆栈都会增长以适应该函数中的局部变量。给定一个使用400字节的 largeObject 类:

void MyFunc(int p1, largeObject p2, largeObject *p3)
{
   int s1;
   largeObject s2;
   largeObject *s3;
}

调用此函数时,您的堆栈将如下所示(详细信息将根据调用约定和体系结构而有所不同):

   [... rest of stack ...]
   [4 bytes for p1] 
   [400 bytes for p2]
   [4 bytes for p3]
   [return address]
   [old frame pointer]
   [4 bytes for s1]
   [400 bytes for s2]
   [4 bytes for s3]

有关堆栈如何运作的一些信息,请参阅 x86呼叫约定。 MSDN还有几个不同的呼叫对流图,有示例代码生成的堆栈图

正如其他人所说,目前尚不清楚“大型物体”是什么意思......然而,既然你接着问

他们只是占用多个堆栈“插槽”?

我假设您只是指大于整数的任何值。不过,正如其他人指出的那样,堆栈没有整数大小的“槽”——它只是内存的一部分,其中的每个字节都有自己的地址。编译器通过地址来跟踪每个变量 第一的 该变量的字节——这是使用取址运算符时获得的值( &var ),而指针的值就是其他变量的地址。编译器还知道每个变量是什么类型(您在声明变量时告诉它)​​,并且它知道每种类型应该有多大——当您编译程序时,它会执行任何必要的数学计算来计算出这些变量有多少空间调用函数时需要变量,并将其结果包含在函数入口点代码(PDaddy 提到的堆栈帧)中。

在C和C ++中,您不应该在堆栈上存储大对象,因为堆栈是有限的(正如您猜测的那样)。每个线程的堆栈通常只有几兆字节或更少(可以在创建线程时指定)。当你打电话给“新”时要创建一个对象,它不会被放在堆栈上 - 而是放在堆上。

堆栈大小有限。通常在创建进程时设置堆栈大小。如果未在CreateThread()调用中另行指定,则该进程中的每个线程将自动获取默认堆栈大小。所以,是的:可以有多个堆栈'插槽',但每个线程只有一个。它们不能在线程之间共享。

如果将大于剩余堆栈大小的对象放入堆栈,则会出现堆栈溢出,并且应用程序将崩溃。

因此,如果您有非常大的对象,请在堆上分配它们,而不是在堆栈上。堆只受虚拟内存量的限制(比堆栈大一些)。

你可以拥有足够大(或足够多)的对象,将它们放在堆栈上是没有意义的。在这种情况下,您可以将对象放在堆上并在堆栈上放置指向它的指针。这是传递值和传递参考之间的差异。

如何定义大对象?我们说的是大于还是小于分配的堆栈空间的大小?

例如,如果你有这样的东西:

void main() {
    int reallyreallybigobjectonthestack[1000000000];
}

根据您的系统,您可能会遇到段错误,因为没有足够的空间来存储对象。否则它会像任何其他对象一样存储。如果您在实际的物理内存中进行通话,那么您不必担心这一点,因为操作系统级别的虚拟内存将负责处理。

此外,堆栈的大小可能不是整数的大小,它完全取决于您的操作系统和应用程序的布局虚拟地址空间

“stack is integer of size”,意思是“堆栈指针是整数的大小”。它指向堆栈的顶部,这是一个巨大的内存区域。好吧,大于整数。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top